Skip to content

Satellite OTA updates — signed, canary-gated firmware delivery across both tiers#18

Merged
KerseyFabrications merged 19 commits into
mainfrom
satellite_ota
Jun 11, 2026
Merged

Satellite OTA updates — signed, canary-gated firmware delivery across both tiers#18
KerseyFabrications merged 19 commits into
mainfrom
satellite_ota

Conversation

@KerseyFabrications

Copy link
Copy Markdown
Contributor

Summary

Adds end-to-end over-the-air firmware updates for both satellite tiers — Tier-1
(Raspberry Pi .deb) and Tier-2 (ESP32-S3) — driven from the DAWN daemon. An
operator stages a signed release; the daemon offers it to eligible devices over
the existing DAP2 WebSocket; each device verifies, applies, and re-registers; the
server commits (or rolls back) on its own authority. Fleet rollouts go one canary
first and only fan out after it proves the image.

  • Offline Ed25519 signing. The signing key never touches the daemon — releases
    are signed off-box with ota-keytool; devices verify against a baked-in public
    keyring and fail closed if none is provisioned.
  • Verify before apply. Every device checks the signed 164-byte manifest, then
    SHA-256s the downloaded image against the signed hash before switching to it.
  • Single-use, platform-bound download tokens (auth_db schema v60), TTL-scoped,
    consumed atomically; HTTPS route with path-traversal containment.
  • Anti-rollback + min_version floor; downgrades require an explicit
    --allow-downgrade.
  • Tier-1 rollback via a boot-count marker + a root-owned launcher outside the
    service's writable paths; Tier-2 via a verify-boot watchdog + dual-partition
    swap.

What's included

  • Phase 1–2 — core + server engine: firmware-version reporting, signed-manifest
    format + ota-keytool, the release store + per-device state machine, and the
    HTTPS / WebSocket / admin transports.
  • Phase 3 — Tier-1 (RPi): device-apply, .deb packaging, Pi build container,
    CI coverage.
  • Phase 4 — Tier-2 (ESP32): reboot-first download, vendored TweetNaCl verify,
    NVS state machine + boot-count rollback, verify-boot watchdog.
  • Phase 5 — fleet rollout: canary→fan-out orchestrator (ota_rollout.c) with a
    WebUI "Fleet Rollout" panel.
  • Runtime release rescan (no daemon restart to pick up a new release) and
    ota-release.sh --allow-downgrade.
  • Build refactor: the daemon is now buildable without WebUI (local/ci
    presets) — moved the always-needed DB/crypto/memory sources out of the WEBUI/AUTH
    blocks, fixed the scheduler weak-symbol fallback, gated webui/messaging/phone
    couplings. smoke_test.sh now covers all four presets and runs in the pre-push hook.

Operator surfaces

  • dawn-admin ota list / push / rescan / push-all / rollout-status / rollout-abort
  • WebUI satellite-management panel: per-device push + a Fleet Rollout panel
    (type + version + allow-downgrade), with live status + abort.
  • ota-release.sh (sign + stage + optional push), ota-keytool (offline keygen/sign).

Testing

  • New unit coverage: test_ota, test_ota_rollout (canary state machine +
    slot-claim invariants), test_ota_apply / _marker / _launch, test_ota_manifest.
  • ctest -L ci 73/73; smoke_test.sh 4/4 presets; builds clean (daemon, keytool,
    Tier-1 satellite, ESP32 CI stub).

Review

Reviewed pre-merge by master-code-reviewer (whole branch) and architecture-reviewer
(fixes + tests) — no Critical/High findings; the actionable Mediums/Lows were folded
in (rollout-start TOCTOU hardening, trust-boundary validation at the DB chokepoint,
keytool header-format + secret-key O_EXCL, WebUI failure feedback, Tier-1 token
charset, postinst stale-marker cleanup).

Deferred (follow-ups, non-blocking)

Audit log of OTA operations; signed allow_downgrade in the manifest (wire-format
change); optional ESP32 native-bootloader rollback; plus the minor review Lows
tracked in TODO.

Satellites report firmware_version + an `ota` capability at registration; the
daemon records them in a new ota_device_state table (auth_db schema v59) and the
WebUI admin panel surfaces "Firmware: x.y.z" per device. Foundation for the
server→satellite OTA system (docs/OTA_DESIGN.md).

- auth_db v59: ota_device_state table; src/core/ota_db.c upsert/get (shared handle)
- webui_satellite: parse+record version+ota cap on register; admin list + JS render
- Tier 1 RPi (satellite_version.h single-source) + Tier 2 ESP32 send version;
  ESP32 partitions.csv + README for the future dual-OTA layout

Validated on hardware: dawn-kitchen (Tier 1) + Office Speaker (Tier 2) both
report 2.0.0 in the WebUI; v59 migration applied on the live daemon.
…-build fixes

The daemon build never compiled dawn_satellite, so config-gated breakage shipped
silently. Add build coverage + a container to build Pi-target binaries locally.

- ci.yml: satellite-build job (headless, apt-only deps — lws/json-c/alsa/opus)
- pre-push.hook: build the satellite before push
- CMakeLists: move include_directories(src) out of the SDL block (ws_client.h
  includes ui/music_types.h unconditionally → headless build was broken)
- voice_processing: guard vad_silero_cleanup on HAVE_VAD_SILERO; drop dead
  TTS-off ensure_tts_loaded stub (-Wunused-function)
- Dockerfile.pibase/pibuild + build-pi.sh: build Pi-ABI binaries (trixie arm64,
  libwebsockets .so.19) from the working tree — no commit/checkout-on-Pi

Validated: headless + full voice+UI builds clean; full binary runs on a Pi.
Trust-model primitive + operator signing CLI for server→satellite OTA, both
host-validated. Daemon-side serving/apply (release store, download route, push)
comes next.

- src/core/ota_manifest.{c,h}: pure libsodium core — fixed little-endian binary
  wire format (not JSON), Ed25519 sign + verify-before-parse against a keyring,
  SHA-256, numeric semver compare, anti-rollback/min_version floor.
- tests/test_ota_manifest.c: 17 unit tests (CI label) — sign/verify roundtrip,
  tamper/wrong-key/signed-garbage rejection, keyring rotation, semver, rollback.
- tools/ota_keytool.c (make ota-keytool): offline keygen/sign/verify +
  pubkey-header; private key never touches the daemon. Validated end-to-end.
Daemon side of server→satellite OTA: it can offer, serve, and track updates
(devices apply in Phase 3/4). [ota].enabled=false by default — inert until opt-in.

- ota_db: device state machine — set_state, single-flight begin_offer,
  one-time consume_token, clear_target, reconcile_stale.
- src/core/ota.c: release-store scan/resolve, ota_begin_push (mints token +
  builds offer), ota_authorize_image_download (token consume + realpath guard),
  server-owned ota_finalize_on_register. No webui dependency.
- [ota] config (parser/defaults/validate/env round-trip + dawn.toml.example).
- Transport (Layer 4): HTTPS image route (/api/ota/.., TLS + one-time token +
  path-hardened), WS device handlers (ota_status/ack/reject) + server→device
  offer push, admin-gated ota_push/ota_list, registration finalize hook.
- tests/test_ota.c: 9 integration tests (single-flight, single-use token,
  path-traversal, finalize, reconcile). Full CI suite 69/69 green.
Add the two operator entry points over the already-shipped OTA engine.

dawn-admin: reserve admin opcode band 0xC0-0xCF; admin_socket_ota.c
(ota list / push, push delegates to webui_ota_push so it shares the
WebUI delivery spine); `dawn-admin ota list` and `ota push --uuid
--version [--allow-downgrade]`.

WebUI: per-online-device version picker + allow-downgrade + Push button
in the satellite panel; fetches ota_list, sends ota_push, renders
in-flight ota_state.

Four-agent review applied: escapeAttr for attribute-context output
(fixes an XSS via attacker-controlled satellite name, incl. the
pre-existing delete-button site); clamped snprintf accumulation;
mobile touch targets; keep-in-sync note on the cross-layer forward decl.

Build clean, test_ota 9/9 + 17/17, CI 69/69. Live round-trip pending a
daemon restart (running daemon predates the 0xC0 dispatch).
…erage

Device side: ota_apply downloads a signed image over WSS, verifies before
parse, and atomically swaps its own ELF; a frozen libc-only launcher
(dawn-satellite-launch) owns boot-count rollback so a dead-on-arrival image
still recovers. Trust anchor is a runtime operator keyring
(/etc/dawn/ota_pubkey, fail-closed) so unsigned prebuilt binaries can ship and
each operator signs for their own fleet.

Distribution: .deb (package-deb.sh, auto-computed runtime deps + bundled
non-apt libs), release.yml CI, and ota-release.sh (offline signing, keys under
/etc/dawn/signing). ota-release.sh refuses a --version that doesn't match the
binary's embedded marker, and defaults min_version empty (anti-skip floor is
opt-in; anti-rollback is automatic).

Server: offer reads manifest+sig fresh from disk (re-stage needs no daemon
restart, no cached/sig skew); register-time finalize resolves a stuck
version-mismatch row to failed; download token is bound to platform
(schema v60) so an rpi token can't fetch an esp32 image.

Tests: host suites for the offer decision, launcher rollback decision, and
marker codec (decision cores for ~10/13 of the device-apply matrix) + a
cross-platform token-binding test. Folds in big-three review fixes
(shared fsync helper, version-mirror static_assert, input validation).

Tested: all OTA unit tests + full CI (72/72) green; live-verified 2.0.0->2.2.0
happy path on dawn-kitchen; v60 migration applied cleanly to the live DB.
The helper pointed at a never-published GitHub "models" release manifest, so
every fetch 404'd. Rework it to download directly over HTTPS from the same
sources setup_models.sh uses — Silero VAD + the Piper "Alba" voice from the
DAWN repo, and the chosen ASR from alphacephei.com (Vosk) or HuggingFace
(Whisper) — into /var/lib/dawn-satellite/models. The default set (VAD + TTS +
Vosk-small) matches the shipped satellite.toml; --asr whisper switches engines.
curl -f makes an HTTP error abort instead of writing an error page over a model.

control: Recommends curl/unzip/ca-certificates (what the helper actually needs)
instead of the nonexistent dawn-satellite-models package.

Tested: all five upstream URLs return 200; a real download yields a valid ONNX
(not an HTML error page) and --create-dirs builds the whisper.cpp/ subdir; arg
validation + sh -n clean.
Completes server→satellite OTA across both tiers. ESP32-S3 device side:
Ed25519 verify-before-parse of the offer, NVS hand-off + reboot-first
WiFi-only download (esp_ota_ops + SHA-256 of committed flash + boot switch),
NVS boot-count rollback guard, and a verify-boot esp_timer watchdog that
force-reboots an image which reaches setup() but can't register — so a hang
(not just a reboot loop) still advances the guard to revert. ota_boot_path
runs before the PSRAM/buffer allocs so the counter advances even when a later
step hangs. Vendored TweetNaCl (verify-only); ota_pubkey.h baked per-operator
(gitignored, .example committed). build-esp32.sh scripts the arduino-cli build
(PSRAM=enabled — this board is QSPI, not OPI) + a CI compile gate. Reg log
reports fw=. Big-three reviewed; fixes folded in (all-zero-key reject,
timer-start failure, clear-before-disarm race, next!=running guard).

Also fixes find_session_by_uuid_unlocked to prefer a live session over a
stale disconnected one (a reboot-churn leftover shadowed the live session and
made `ota push` falsely report "Device is offline").

Test: live-verified end-to-end on ESP32-S3 — clean 2.x update through to daemon
committed/WebUI success, AND the watchdog rollback (a deliberately-broken push
hung in setup() and self-reverted to the prior slot, no reflash). Daemon +
firmware compile clean (0 warnings); format --check --changed clean.

Daemon VERSION_NUMBER 1.0.0→2.0.0, RPi satellite firmware 2.2.0→2.0.0, to
match the ESP32 satellite — one coherent 2.0.0 across the suite.
Avoids restarting the daemon to consume a freshly-staged release. New
ota_rescan() reloads the release dir into the in-memory store; exposed as admin
opcode 0xC2 + `dawn-admin ota rescan`, and ota-release.sh auto-rescans after
staging (best-effort) so a new version is pushable immediately. The release
store gains a mutex (find_release copies out under the lock) since rescan is now
a runtime writer concurrent with push/list reads.

Also adds --allow-downgrade to ota-release.sh, passing through to
`dawn-admin ota push --allow-downgrade` (downgrade pushes previously needed the
WebUI toggle).

Test: live — staged + auto-rescanned + pushed esp32/2.1.0 with no restart
(daemon reported 5 releases, device updated + committed). Build + format clean.
The 2.0.0 bump exposed a dual definition — CMake passed
-DVERSION_NUMBER="1.0.0" while include/version.h defined "2.0.0", warning on
every TU including the header. Drop the CMake set/-D (keep GIT_SHA's) so
version.h is the sole source, and add the missing version.h include to tui.c,
which had relied on the -D. Build is now warning-free.
`dawn-admin ota push-all --platform <p> --version <v>` rolls a release out to a
whole platform/tier safely: one canary first, and the rest fan out only after it
re-registers reporting the new version. A canary revert or 600s timeout halts the
rollout — the rest are never touched. Adds `ota rollout-status` / `rollout-abort`.

New src/core/ota_rollout.c orchestrator: single in-memory rollout, mutex-guarded.
Stays out of the webui layer per ota.h's rule — delivery is a callback dawn.c
registers (webui_ota_push, under ENABLE_WEBUI). Commit/fail events hooked into
ota_finalize_on_register only flip state; the fan-out push is deferred to a
once-per-second main-loop tick so offers are never delivered while the
registration handler holds session locks. Admin opcodes 0xC3-0xC5. Eligibility =
online devices of the tier not already on the target (offline skipped;
offer-pending-on-reconnect is a future add).

Also updates OTA_DESIGN §8/Phase 4 to reflect the verify-boot watchdog as shipped.

master-code-reviewer pass applied (ENABLE_WEBUI build guard, abort/finish state
races, overflow accounting). test_ota gets no-op stubs for the new ota.c rollout
hooks. tests-ci builds clean, test_ota passes 11/11, format clean. Canary path is
testable with one device; the fan-out wave needs a 2nd esp32 to exercise live.
Adds a "Fleet Rollout" panel at the bottom of the satellite management area:
pick a satellite type (ESP32/RPi) + version + allow-downgrade and roll a release
out to the whole fleet (canary first). A status line polls every 4s during a
rollout with an Abort button, and reflects an in-flight rollout on page load.

Backend: three admin-gated WS handlers (ota_push_all / ota_rollout_status /
ota_rollout_abort) in webui_ota.c -> ota_rollout_*, dispatched in
webui_message_dispatch.c, routed in dawn.js.

Frontend (satellites.js + satellites.css): reuses the per-device OTA control's
layout primitives, confirm-modal pattern, and CSS tokens. ui-design-architect
reviewed; applied a11y + UX fixes (aria-live status region, aria-label on the
selects incl. the per-device one, danger-styled confirm + double-submit guard on
the whole-fleet action, mobile touch-target sizing).

Live-verified from the browser: push-all 2.3.0 to esp32 -> canary committed ->
status "done". Format clean.
smoke_test.sh's local/full presets failed to link. Make the daemon buildable
without WebUI, wire the smoke test into pre-push so it stays that way, and show
the Tier-1 firmware version in its settings panel.

Build/config:
- calendar_service.c hard-called email_db_account_list; guard with
  #ifdef DAWN_ENABLE_EMAIL_TOOL (calendar can be built with email off).
- Move embedding_engine + the DB-layer sources the always-compiled memory/OTA
  subsystems depend on (auth_db_conv/user/settings/satellite, image_store) out
  of the ENABLE_WEBUI/ENABLE_AUTH blocks into always-compiled.
- Detect + link libsodium unconditionally (crypto_store/calendar always use it).
- scheduler.c weak broadcast fallbacks were #ifdef ENABLE_WEBUI (backwards) so
  the scheduler couldn't build standalone -> flip to #ifndef.
- Gate webui_oauth.c, the messaging tool registration, and phone_service's
  session/SMS calls on ENABLE_WEBUI.

Pre-push hook:
- After tests-ci + the satellite build, run smoke_test.sh, which links the full
  dawn binary across all four presets (tests-ci links only the test binaries, so
  a daemon that won't link without WebUI slipped through). Gated on the four
  smoke build dirs existing so it never forces a cold build in the hook.

Satellite:
- Show DAWN_SATELLITE_FIRMWARE_VERSION in the Tier-1 settings pull-down panel.

Test: smoke_test.sh passes all four presets (local/full/debug/debug+email);
WebUI-on presets unaffected; daemon + satellite build clean; sdl_ui syntax-checked.
OTA_DESIGN.md: add the runtime release rescan (dawn-admin ota rescan / opcode
0xC2 + ota-release.sh auto-rescan -> pushable without a daemon restart) to the
operator-surfaces section, and the WebUI Fleet Rollout panel to the Phase 5
shipped notes. Both shipped but were undocumented.

dawn_satellite_arduino/README.md: the publish flow said "restart the daemon if
it hasn't scanned the new release dir" -- obsolete now that staging auto-rescans.
Reflect auto-rescan + --allow-downgrade + the stage-then-Fleet-Rollout path.
Addresses master-code-reviewer + architecture-reviewer findings on the
satellite_ota branch. No security invariants changed; correctness/robustness.

Rollout (ota_rollout.c):
- Close the start TOCTOU: two threads (admin + WebUI) could both pass the
  busy-check and both start a rollout, the 2nd orphaning the 1st. Claim the
  single-rollout slot atomically via a new RO_STARTING state; release it on any
  post-claim failure (RO_FAIL_CLAIMED) and bail the arm step if aborted mid-start.
- Render RO_STARTING as "Rollout starting…" instead of an empty summary.

Trust boundary:
- Reject unknown satellite-reported OTA state tokens (ota_state_is_valid) — an
  unknown token would dodge the in-flight predicate. Width-clamp the free-text
  error/reason/version at the DB chokepoint (ota_db_set_state /
  ota_db_report_version) so every caller gets one enforced bound.

Keytool (ota_keytool.c):
- pubkey-header now emits the OTA_PUBKEY_HEX[] form the ESP32 sketch #includes
  (was an uncompilable uint8_t[][32]).
- Write the signing secret key with open(O_CREAT|O_EXCL, 0600)+fdopen (no
  world-readable window, no symlink/pre-created-file reuse).

WebUI:
- Resume rollout-status polling for a rollout discovered after reload / in
  another tab (was only ever stopped).
- Surface OTA push/rollout failures as a panel toast + re-enable the buttons
  (routed from dawn.js OTA_ERROR) instead of the chat transcript.

Satellite / packaging:
- Tier-1: charset-validate the download token (hex) before it's spliced into the
  URL, mirroring the uuid guard.
- postinst: clear the stale ota/pending marker after re-seeding the binary so an
  apt action can't trip a probation rollback.

Tests:
- New tests/test_ota_rollout.c (8 cases) — first coverage of ota_rollout.c:
  slot-release on no-eligible + push-fail (the TOCTOU regression), second-start
  rejection, canary->fan-out->done, canary-revert-halt, abort, no-transport.
- test_ota: ota_state_is_valid; test_ota_apply: bad-token reject.

Test: daemon + keytool + Tier-1 satellite build clean; ctest -L ci 73/73;
format clean.
@qodo-code-review

qodo-code-review Bot commented Jun 11, 2026

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (1) 📘 Rule violations (3)

Context used
✅ Compliance rules (platform): 27 rules

Grey Divider


Action required

1. Restart fails pending offers ✓ Resolved 🐞 Bug ≡ Correctness
Description
On daemon startup, ota_db_reconcile_stale rewrites stale rows in the in-flight predicate (including
state='offered') to state='unknown', and ota_finalize_on_register later interprets any non-'offered'
state with a pending target_version as evidence the update already ran and marks it 'failed'. This
can incorrectly fail still-pending offers after a restart, breaking rollouts and requiring operator
re-pushes.
Code

src/core/ota_db.c[R184-187]

+/* In-flight states a fresh offer must not interrupt (single-flight guard). */
+#define OTA_INFLIGHT_PREDICATE \
+   "state IN ('offered', 'downloading', 'verifying', 'applying', 'rebooting')"
+
Evidence
The in-flight predicate includes offered, reconciliation rewrites any stale in-flight row to
unknown on startup, and registration finalization marks any pending target with state != offered
as failed when the device reports a different version—creating a restart-driven false failure for
still-pending offers.

src/core/ota_db.c[184-187]
src/core/ota_db.c[324-334]
src/core/ota.c[213-220]
src/core/ota.c[463-501]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
On startup, `ota_db_reconcile_stale()` uses `OTA_INFLIGHT_PREDICATE`, which currently includes the `offered` state, and rewrites stale rows to `unknown`. Later, `ota_finalize_on_register()` treats any pending target where `state != offered` as a failed update if the device registers reporting a different version. After a restart, a device that simply never acted on an offer can be incorrectly marked failed.

## Issue Context
- Reconciliation is intended to handle mid-update rows orphaned by a daemon restart.
- `offered` is not necessarily “mid-update”; it’s a pending offer that may legitimately sit for longer than the stale threshold.

## Fix Focus Areas
- Adjust reconciliation to exclude `offered` (or use a separate predicate for reconciliation).
 - src/core/ota_db.c[184-187]
 - src/core/ota_db.c[324-334]
- Alternatively (or additionally), tighten `ota_finalize_on_register()` so it only auto-fails when the state is in a post-offer, truly in-progress set (e.g. downloading/verifying/applying/rebooting), and not for `unknown`.
 - src/core/ota.c[463-501]
- Ensure startup reconciliation call remains correct under the new semantics.
 - src/core/ota.c[213-220]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


2. ota_rollout_set_push_fn() missing Doxygen ✓ Resolved 📘 Rule violation ✧ Quality
Description
The public API function ota_rollout_set_push_fn() is declared without an immediately preceding
Doxygen-style comment block with @param/@return tags. This breaks the requirement that all
public API functions be documented in a Doxygen-recognized format.
Code

include/core/ota_rollout.h[R54-62]

+/**
+ * @brief Register the per-device delivery function (transport layer, Layer 4).
+ *
+ * Called once at init with webui_ota_push.  Until set, ota_rollout_start() fails
+ * (no way to deliver an offer).  Signature matches webui_ota_push():
+ *   returns AUTH_DB_SUCCESS | AUTH_DB_LOCKED | AUTH_DB_NOT_FOUND | AUTH_DB_FAILURE.
+ */
+typedef int (*ota_rollout_push_fn)(const char *uuid, const char *version, bool allow_downgrade);
+void ota_rollout_set_push_fn(ota_rollout_push_fn fn);
Evidence
PR Compliance ID 278940 requires a Doxygen-style block immediately preceding each public API
declaration. In include/core/ota_rollout.h, the Doxygen block is followed by a typedef, and the
ota_rollout_set_push_fn() declaration has no directly-attached Doxygen comment.

Rule 278940: Require Doxygen-style comments for all public API functions
include/core/ota_rollout.h[54-62]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`ota_rollout_set_push_fn()` is a public header declaration and needs an immediately preceding Doxygen comment block, including `@param` for its parameters and `@return` if non-void.

## Issue Context
The existing Doxygen block is separated from the function declaration by a typedef, so documentation tools may not associate it with the function.

## Fix Focus Areas
- include/core/ota_rollout.h[54-63]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


3. tweetnacl.c missing GPL header 📘 Rule violation § Compliance
Description
The newly added files dawn_satellite_arduino/tweetnacl.c and dawn_satellite_arduino/tweetnacl.h
start immediately with code (includes/guards/macros) and omit the required GPLv3 header block at the
top. This can cause licensing/audit compliance failures for newly added C/C++ source and header
files.
Code

dawn_satellite_arduino/tweetnacl.c[R1-6]

+#include "tweetnacl.h"
+#define FOR(i,n) for (i = 0;i < n;++i)
+#define sv static void
+
+typedef unsigned char u8;
+typedef unsigned long u32;
Evidence
PR Compliance ID 278939 requires the GPLv3 header in every new C/C++ source/header file, but the
added dawn_satellite_arduino/tweetnacl.c begins directly with #include "tweetnacl.h" and
dawn_satellite_arduino/tweetnacl.h begins directly with #ifndef TWEETNACL_H, with no GPL header
appearing before those first code lines in either file.

Rule 278939: Require GPL v3 license header in new C/C++ source files
dawn_satellite_arduino/tweetnacl.c[1-6]
dawn_satellite_arduino/tweetnacl.h[1-12]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The newly added files `dawn_satellite_arduino/tweetnacl.c` and `dawn_satellite_arduino/tweetnacl.h` are missing the mandated GPLv3 header block at the top of the file before any includes, macros, or header guards.

## Issue Context
Per PR Compliance ID 278939 and the repository’s standard GPLv3 header template (per CLAUDE.md), every new `.c`/`.h`/`.cpp` file must include the GPLv3 header at the top to avoid licensing/audit compliance issues.

## Fix Focus Areas
- dawn_satellite_arduino/tweetnacl.c[1-20]
- dawn_satellite_arduino/tweetnacl.h[1-25]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


View more (3)
4. ota_rollout_push_fn typedef lacks _t ✓ Resolved 📘 Rule violation ✧ Quality
Description
The new typedef ota_rollout_push_fn does not end with the required _t suffix. This violates the
project typedef naming convention and can lead to inconsistent API/type naming.
Code

include/core/ota_rollout.h[R61-62]

+typedef int (*ota_rollout_push_fn)(const char *uuid, const char *version, bool allow_downgrade);
+void ota_rollout_set_push_fn(ota_rollout_push_fn fn);
Evidence
PR Compliance ID 278923 requires all new/modified typedef alias names to end with _t. The added
typedef uses the alias ota_rollout_push_fn, which does not have the _t suffix.

Rule 278923: C/C++ typedef names must use _t suffix
include/core/ota_rollout.h[61-62]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The new typedef alias `ota_rollout_push_fn` does not end with `_t`.

## Issue Context
Project convention requires all typedef aliases to use the `_t` suffix.

## Fix Focus Areas
- include/core/ota_rollout.h[61-62]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


5. ws_client_send_ota_ack() returns -1 📘 Rule violation ≡ Correctness
Description
The new OTA websocket helpers return negative values (e.g., -1) to signal invalid
arguments/errors. This violates the project rule disallowing negative return values as an error
signaling mechanism.
Code

dawn_satellite/src/ws_client.c[R1217-1220]

+int ws_client_send_ota_ack(ws_client_t *client) {
+   if (!client) {
+      return -1;
+   }
Evidence
PR Compliance ID 278934 forbids using negative numeric return values (like -1) to indicate errors.
The newly added ws_client_send_ota_ack() returns -1 when client is NULL.

Rule 278934: Do not use negative return values to signal errors
dawn_satellite/src/ws_client.c[1217-1220]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
New functions use `return -1;` as an error sentinel (e.g., when `client` is NULL). The project disallows negative return values for error signaling.

## Issue Context
These OTA send helpers are new API surface used by OTA code paths.

## Fix Focus Areas
- dawn_satellite/src/ws_client.c[1217-1247]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


6. persistentUUID not snake_case 📘 Rule violation ✧ Quality
Description
The modified global identifier persistentUUID is not snake_case (contains uppercase letters). This
violates the naming convention for C/C++ identifiers in modified code.
Code

dawn_satellite_arduino/dawn_satellite_arduino.ino[R171-173]

+/* External linkage (not static): ota_apply.cpp references it via `extern char
+ * persistentUUID[]` to build the download URL. */
+char persistentUUID[37] = {0};
Evidence
PR Compliance ID 278924 requires newly added/modified C/C++ variable identifiers to match snake_case
(^[a-z][a-z0-9_]*$). The added declaration char persistentUUID[37] = {0}; contains uppercase
letters and is therefore non-compliant.

Rule 278924: Enforce snake_case for C/C++ function and variable identifiers
dawn_satellite_arduino/dawn_satellite_arduino.ino[171-173]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The modified global variable `persistentUUID` violates the required snake_case naming convention.

## Issue Context
This identifier is newly modified (changed linkage) in this PR, so it falls under the rule scope for modified variables.

## Fix Focus Areas
- dawn_satellite_arduino/dawn_satellite_arduino.ino[171-173]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Remediation recommended

7. Token burned before serving 🐞 Bug ☼ Reliability
Description
ota_authorize_image_download consumes and clears the one-time download token (and advances state to
'downloading') before verifying the image path resolves via realpath(). If realpath() fails (missing
file, permission, transient FS error), the device's single-use token is burned and the request must
be re-offered.
Code

src/core/ota.c[R406-426]

+   /* Validate path components BEFORE consuming the token (a malformed path must
+    * not burn the device's one-time token). */
+   if (str_to_platform(platform_str) == OTA_PLATFORM_UNKNOWN || !version_is_safe(version)) {
+      return FAILURE;
+   }
+
+   if (ota_db_consume_token(uuid, platform_str, version, token, time(NULL)) != AUTH_DB_SUCCESS) {
+      OLOG_WARNING("ota: download token rejected for %s %s/%s", uuid, platform_str, version);
+      return FAILURE;
+   }
+
+   char raw[OTA_PATH_MAX];
+   snprintf(raw, sizeof(raw), "%s/%s/%s/image", g_config.ota.release_dir, platform_str, version);
+
+   /* Defense in depth: canonicalize and confirm the resolved path is inside the
+    * release dir (realpath also requires the image to exist). */
+   char canon_base[PATH_MAX];
+   char canon_path[PATH_MAX];
+   if (!realpath(g_config.ota.release_dir, canon_base) || !realpath(raw, canon_path)) {
+      return FAILURE;
+   }
Evidence
The server consumes the token before path canonicalization, and token consumption explicitly clears
the token and sets state='downloading', so any later failure in path resolution occurs after the
token has been irrevocably used.

src/core/ota.c[397-426]
src/core/ota_db.c[252-274]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`ota_authorize_image_download()` consumes the OTA download token before confirming the image path can be resolved/canonicalized. When the subsequent `realpath()` checks fail, the one-time token has already been cleared and the device is moved to `downloading`.

## Issue Context
- `ota_db_consume_token()` updates the DB row to `token=NULL, state='downloading'`.
- `realpath()` can fail for reasons unrelated to token validity (missing image file, permissions, transient filesystem conditions).

## Fix Focus Areas
- Reorder checks so filesystem/path resolution succeeds before consuming the token.
 - src/core/ota.c[397-435]
- Consider consuming only after successfully opening the file for serving (or returning an open fd) to reduce “token burned but download can’t start” cases.
 - src/webui/webui_http.c[1279-1318]
- If token consumption must remain early, add rollback behavior on later failure (e.g., re-issuing a token or restoring state) so devices aren’t forced into manual re-offers.
 - src/core/ota_db.c[252-295]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

Qodo Logo

@qodo-code-review

Copy link
Copy Markdown

PR Summary by Qodo

Signed OTA updates with canary-gated rollouts for RPi and ESP32 satellites
✨ Enhancement 🧪 Tests 📝 Documentation ⚙️ Configuration changes 🕐 40+ Minutes

Grey Divider

Walkthroughs

Description
• Add signed OTA firmware offers, token-gated downloads, and server-owned commit/rollback.
• Support both Tier-1 (RPi/.deb) and Tier-2 (ESP32) apply paths with anti-rollback.
• Add canary-first fleet rollout controls in WebUI and dawn-admin, with new unit tests.
Diagram
graph TD
A["Operator (WebUI / dawn-admin)"] --> B["DAWN daemon OTA"] --> F[("auth_db (SQLite)")]
C["Offline ota-keytool"] --> E["Release store (disk)"] --> B
B --> H["HTTPS /api/ota image"] --> E
G["Satellites (RPi / ESP32)"] -->|"WS: offer/status/register"| B
G -->|"GET image + token"| H
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. Adopt TUF/Uptane-style metadata framework
  • ➕ Stronger compromise resilience (roles, threshold signing, metadata expiry)
  • ➕ Established OTA security model and tooling ecosystem
  • ➖ Substantially more complexity and metadata management
  • ➖ Heavier footprint for constrained devices (ESP32) and custom integration effort
2. Persist rollout state in the DB (restart-survivable)
  • ➕ Rollouts survive daemon restarts and can be audited historically
  • ➕ Enables richer UI reporting and post-mortems
  • ➖ Requires additional schema, idempotency, and retry semantics
  • ➖ More failure modes than the current intentionally-simple in-memory single-rollout
3. Use tier-specific native update channels (apt repo for RPi)
  • ➕ Leverages mature package distribution and upgrade tooling for Tier-1
  • ➕ Potentially reduces custom delivery logic for Tier-1
  • ➖ Splits operator workflow across tiers
  • ➖ Does not help Tier-2; still need a signed OTA design for ESP32

Recommendation: The PR’s approach (fixed-size signed manifest + token-gated HTTPS fetch + server-owned commit on re-registration) is a strong fit for constrained devices and keeps both tiers on a consistent trust model. Canary-first fleet rollout adds meaningful safety with minimal operational overhead. Consider DB-persisted rollout/audit logging later if restart-survivability and compliance reporting become requirements.

Grey Divider

File Changes

Enhancement (44)
main.c Add 'dawn-admin ota' operator commands +160/-0

Add 'dawn-admin ota' operator commands

• Adds CLI subcommands for OTA release listing, per-device push, release-store rescan, push-all rollout start, and rollout status/abort.

dawn-admin/main.c


socket_client.c Implement admin-socket client requests for OTA +87/-0

Implement admin-socket client requests for OTA

• Implements client-side admin-socket calls used by dawn-admin to invoke OTA list/push/rescan/rollout operations.

dawn-admin/socket_client.c


socket_client.h Expose OTA admin-socket client API +72/-0

Expose OTA admin-socket client API

• Adds declarations for the OTA admin client helpers used by dawn-admin’s CLI implementation.

dawn-admin/socket_client.h


ota_apply.h Define Tier-1 OTA apply interface +101/-0

Define Tier-1 OTA apply interface

• Adds the public header for Tier-1 OTA offer handling and apply logic integration points.

dawn_satellite/include/ota_apply.h


ota_apply_internal.h Add Tier-1 OTA internal constants and helpers +129/-0

Add Tier-1 OTA internal constants and helpers

• Defines internal buffers/limits and helper contracts used across Tier-1 OTA implementation files.

dawn_satellite/include/ota_apply_internal.h


ota_launch.h Add Tier-1 OTA launcher interface +68/-0

Add Tier-1 OTA launcher interface

• Adds interfaces for a launcher path used to support rollback-safe execution on Tier-1.

dawn_satellite/include/ota_launch.h


ota_marker.h Add Tier-1 OTA marker format/API +114/-0

Add Tier-1 OTA marker format/API

• Defines persistent marker structures/APIs used for boot-count tracking and rollback decisions on Tier-1.

dawn_satellite/include/ota_marker.h


satellite_version.h Add Tier-1 firmware version header +36/-0

Add Tier-1 firmware version header

• Introduces a single-source version header so Tier-1 can report firmware_version and tooling can introspect versions reliably.

dawn_satellite/include/satellite_version.h


ws_client.h Extend satellite WS client surface for OTA +10/-0

Extend satellite WS client surface for OTA

• Adds/adjusts declarations needed for OTA offer/status messaging in the Tier-1 WS client.

dawn_satellite/include/ws_client.h


main.c Report firmware version/capability from Tier-1 satellite +19/-2

Report firmware version/capability from Tier-1 satellite

• Extends satellite registration/reporting to include firmware_version and OTA capability metadata used by the daemon’s OTA subsystem.

dawn_satellite/src/main.c


ota_apply.c Implement Tier-1 OTA apply pipeline +504/-0

Implement Tier-1 OTA apply pipeline

• Implements Tier-1 offer execution: download the artifact, verify against signed metadata, apply the update, and report status transitions.

dawn_satellite/src/ota_apply.c


ota_apply_decide.c Implement Tier-1 OTA offer validation logic +271/-0

Implement Tier-1 OTA offer validation logic

• Adds pure decision/validation logic for accepting offers (anti-rollback/min_version/downgrade gates and compatibility checks).

dawn_satellite/src/ota_apply_decide.c


ota_launch.c Add Tier-1 OTA launcher entrypoint +70/-0

Add Tier-1 OTA launcher entrypoint

• Introduces a launcher path used to support rollback-safe startup and separation from writable service paths.

dawn_satellite/src/ota_launch.c


ota_launch_core.c Add Tier-1 launcher core implementation +154/-0

Add Tier-1 launcher core implementation

• Implements the core launcher behavior used by Tier-1 OTA rollback strategy (marker interactions and guarded execution).

dawn_satellite/src/ota_launch_core.c


ota_marker.c Implement Tier-1 marker persistence and rollback tracking +235/-0

Implement Tier-1 marker persistence and rollback tracking

• Implements reading/writing boot markers and boot-count tracking used to detect failed boots and enable rollback behavior.

dawn_satellite/src/ota_marker.c


ws_client.c Add Tier-1 WS OTA message handling +85/-0

Add Tier-1 WS OTA message handling

• Adds client-side WebSocket support to receive ota_offer messages and send ota_status/ack/reject updates back to the daemon.

dawn_satellite/src/ws_client.c


dawn_satellite_arduino.ino Wire OTA offer/apply flow into ESP32 sketch +63/-8

Wire OTA offer/apply flow into ESP32 sketch

• Extends the main Arduino sketch to integrate OTA offer reception, firmware version reporting, and OTA apply control flow.

dawn_satellite_arduino/dawn_satellite_arduino.ino


ota_apply.cpp Implement ESP32 reboot-first OTA apply with rollback guard +778/-0

Implement ESP32 reboot-first OTA apply with rollback guard

• Implements Tier-2 OTA logic: verify the signed manifest, persist authenticated metadata in NVS, reboot into a minimal download path, apply to OTA partition, and rollback via boot-count/watchdog guard.

dawn_satellite_arduino/ota_apply.cpp


ota_apply.h Add ESP32 OTA apply configuration/header +44/-0

Add ESP32 OTA apply configuration/header

• Defines constants and interfaces used by the ESP32 OTA implementation (size caps, state, and apply hooks).

dawn_satellite_arduino/ota_apply.h


satellite_version.h Add Tier-2 firmware version header +19/-0

Add Tier-2 firmware version header

• Introduces a version header used for firmware_version reporting and update decision logic on ESP32.

dawn_satellite_arduino/satellite_version.h


admin_socket.h Add OTA admin-socket command definitions +48/-0

Add OTA admin-socket command definitions

• Adds admin-socket opcodes/payload definitions for OTA list/push/rescan/rollout operations.

include/auth/admin_socket.h


admin_socket_internal.h Add internal declarations for OTA admin handlers +9/-0

Add internal declarations for OTA admin handlers

• Adds internal admin-socket handler declarations needed to route OTA commands in the daemon.

include/auth/admin_socket_internal.h


ota.h Add daemon OTA engine API +155/-0

Add daemon OTA engine API

• Introduces the Layer-2 OTA interface: init/rescan, release enumeration, offer minting, image download authorization, status reporting, and commit-on-register hooks.

include/core/ota.h


ota_db.h Add OTA per-device DB/state-machine API +159/-0

Add OTA per-device DB/state-machine API

• Defines the ota_device_state model, state tokens, and DB entrypoints for reporting versions, single-flight offers, token consumption, and stale reconciliation.

include/core/ota_db.h


ota_manifest.h Add signed-manifest primitive (wire format + crypto API) +173/-0

Add signed-manifest primitive (wire format + crypto API)

• Defines the fixed-size manifest wire format, Ed25519 signing/verification APIs, semver comparison, and anti-rollback rules.

include/core/ota_manifest.h


ota_rollout.h Add canary-first rollout orchestration API +128/-0

Add canary-first rollout orchestration API

• Defines the rollout orchestrator interface (start/status/abort/tick) and the push callback contract to keep the engine transport-agnostic.

include/core/ota_rollout.h


webui_ota.h Add WebUI OTA transport declarations +65/-0

Add WebUI OTA transport declarations

• Defines WebUI-layer OTA entrypoints used by both WebUI and admin-socket surfaces to deliver OTA offers consistently.

include/webui/webui_ota.h


admin_socket.c Wire OTA opcodes into admin socket dispatch +14/-0

Wire OTA opcodes into admin socket dispatch

• Registers and routes new OTA admin-socket commands to their handler implementations.

src/auth/admin_socket.c


admin_socket_ota.c Implement OTA admin command handlers +232/-0

Implement OTA admin command handlers

• Adds daemon-side handlers for OTA list/push/rescan/push-all/rollout status/abort, delegating per-device push to the WebUI OTA path for consistency.

src/auth/admin_socket_ota.c


auth_db_schema.c Add ota_device_state table and target_platform migration (v59/v60) +75/-1

Add ota_device_state table and target_platform migration (v59/v60)

• Adds the ota_device_state table for per-device OTA tracking and migrates to include target_platform so download tokens are platform-bound.

src/auth/auth_db_schema.c


ota.c Implement OTA engine: release store, offers, download auth, commit-on-register +502/-0

Implement OTA engine: release store, offers, download auth, commit-on-register

• Implements runtime release scanning/rescan, offer minting (including safe path handling), one-time token-gated download authorization, and server-owned finalize-on-register commit/failure logic.

src/core/ota.c


ota_db.c Implement OTA DB state machine operations +354/-0

Implement OTA DB state machine operations

• Implements DB operations for ota_device_state: version reporting, state transitions, single-flight offer claiming, token consumption, and stale reconciliation with auth_db locking.

src/core/ota_db.c


ota_manifest.c Implement pure signed-manifest serialization and verification +281/-0

Implement pure signed-manifest serialization and verification

• Implements canonical manifest wire format, Ed25519 sign/verify against a keyring, SHA-256 helper, semver compare, and anti-rollback policy utilities designed to be reusable across daemon and satellites.

src/core/ota_manifest.c


ota_rollout.c Implement canary-gated fleet rollout orchestrator +506/-0

Implement canary-gated fleet rollout orchestrator

• Implements a single active rollout state machine: choose eligible devices, push canary first, fan out only after commit, and support timeout/status/abort behavior.

src/core/ota_rollout.c


dawn.c Initialize and tick OTA subsystem in daemon lifecycle +27/-0

Initialize and tick OTA subsystem in daemon lifecycle

• Wires OTA initialization (and periodic rollout ticking) into the daemon startup/runtime loop so OTA becomes active when enabled by config.

src/dawn.c


webui_admin_satellite.c Expose OTA-related satellite fields in admin views +16/-0

Expose OTA-related satellite fields in admin views

• Extends the admin satellite view logic to surface OTA-related device metadata (e.g., firmware version/state) for operators.

src/webui/webui_admin_satellite.c


webui_http.c Add OTA HTTPS image download endpoint +47/-0

Add OTA HTTPS image download endpoint

• Adds GET /api/ota/<platform>/<version>/image handling with optional TLS requirement, rate limiting, strict path parsing, and token consumption via the OTA engine before serving the image file.

src/webui/webui_http.c


webui_message_dispatch.c Dispatch OTA WebSocket messages +23/-0

Dispatch OTA WebSocket messages

• Adds message dispatch routing for OTA admin and satellite message types into their OTA handlers.

src/webui/webui_message_dispatch.c


webui_ota.c Add WebUI OTA transport (admin commands + satellite status) +314/-0

Add WebUI OTA transport (admin commands + satellite status)

• Implements WebSocket handlers for OTA list/push/push-all/rollout status/abort and satellite ota_status/ack/reject, and provides a shared webui_ota_push entrypoint used by both WebUI and dawn-admin surfaces.

src/webui/webui_ota.c


webui_satellite.c Record firmware_version and finalize OTA on registration +33/-3

Record firmware_version and finalize OTA on registration

• Updates satellite registration to record firmware_version into ota_device_state and invoke server-owned finalize-on-register logic to commit or fail in-flight updates.

src/webui/webui_satellite.c


ota_keytool.c Add offline keygen/signing tool for OTA manifests +390/-0

Add offline keygen/signing tool for OTA manifests

• Introduces 'ota-keytool' for offline Ed25519 key generation, device public-key header emission, and release manifest signing; includes safer secret-key file handling.

tools/ota_keytool.c


satellites.css Add styling for OTA controls in satellite admin UI +184/-3

Add styling for OTA controls in satellite admin UI

• Adds CSS to support per-device OTA actions and the fleet rollout panel in the satellites admin page.

www/css/components/satellites.css


satellites.js Add WebUI OTA controls (per-device push + fleet rollout panel) +433/-4

Add WebUI OTA controls (per-device push + fleet rollout panel)

• Adds WebSocket requests and UI controls for listing OTA releases, pushing an update to a device (with allow-downgrade), and starting/monitoring/aborting canary-gated fleet rollouts.

www/js/admin/satellites.js


dawn.js Add WebUI WS message wiring for OTA responses +28/-0

Add WebUI WS message wiring for OTA responses

• Extends the base WebUI WebSocket client wiring to handle OTA-related message types used by the admin satellites page.

www/js/dawn.js


Bug fix (2)
voice_processing.c Fix build guards in voice processing code +4/-6

Fix build guards in voice processing code

• Adjusts optional feature guards and removes/avoids unused code paths to keep satellite builds warning-clean across presets.

dawn_satellite/src/voice_processing.c


scheduler.c Fix minor scheduler behavior for refactored builds +7/-2

Fix minor scheduler behavior for refactored builds

• Small scheduler change to keep behavior/builds correct under the refactored preset and symbol wiring.

src/core/scheduler.c


Refactor (7)
sdl_ui.c Minor satellite UI adjustments +7/-3

Minor satellite UI adjustments

• Small UI-side tweaks to keep the satellite build consistent with new includes/build wiring introduced with OTA work.

dawn_satellite/src/ui/sdl_ui.c


session_manager.h Expose session lookup used by OTA rollout online checks +1/-0

Expose session lookup used by OTA rollout online checks

• Adds a small API surface change so the rollout engine can check whether a device is currently online.

include/core/session_manager.h


session_manager.c Minor session manager changes supporting rollout online checks +17/-2

Minor session manager changes supporting rollout online checks

• Small updates so OTA rollout logic can safely check online sessions without dragging in additional dependencies.

src/core/session_manager.c


calendar_service.c Minor decoupling for new build presets +6/-1

Minor decoupling for new build presets

• Small adjustment to keep tool services compiling cleanly with the updated build preset matrix.

src/tools/calendar_service.c


phone_service.c Minor decoupling for new build presets +12/-1

Minor decoupling for new build presets

• Small adjustment to keep phone tool/service wiring clean under the refactored build gating.

src/tools/phone_service.c


tools_init.c Adjust tool initialization wiring for preset matrix +5/-1

Adjust tool initialization wiring for preset matrix

• Small refactor to initialization flow to align with updated build-time feature gating.

src/tools/tools_init.c


tui.c Minor TUI compilation/wiring tweak +1/-0

Minor TUI compilation/wiring tweak

• Small change to keep TUI mode aligned with updated build wiring and includes.

src/ui/tui.c


Tests (7)
CMakeLists.txt Register OTA tests in the test build +57/-0

Register OTA tests in the test build

• Adds new OTA-related unit test binaries to the test build configuration.

tests/CMakeLists.txt


test_ota.c Add unit tests for OTA engine behavior +306/-0

Add unit tests for OTA engine behavior

• Adds unit test coverage for OTA engine/state behaviors and invariants.

tests/test_ota.c


test_ota_apply.c Add unit tests for Tier-1 OTA apply logic +329/-0

Add unit tests for Tier-1 OTA apply logic

• Adds tests validating Tier-1 OTA apply decision logic and safety gates.

tests/test_ota_apply.c


test_ota_launch.c Add unit tests for Tier-1 launcher behavior +207/-0

Add unit tests for Tier-1 launcher behavior

• Adds tests validating Tier-1 launcher behavior and rollback-related invariants.

tests/test_ota_launch.c


test_ota_manifest.c Add unit tests for manifest wire format and verification +264/-0

Add unit tests for manifest wire format and verification

• Adds tests for manifest serialization/deserialization, signing/verifying, semver handling, and anti-rollback behavior.

tests/test_ota_manifest.c


test_ota_marker.c Add unit tests for Tier-1 OTA marker handling +185/-0

Add unit tests for Tier-1 OTA marker handling

• Adds tests for marker persistence/codec and boot-count rollback logic on Tier-1.

tests/test_ota_marker.c


test_ota_rollout.c Add unit tests for canary rollout state machine +251/-0

Add unit tests for canary rollout state machine

• Adds tests covering rollout start/slot-claim invariants, canary gating, fan-out, timeout, and abort/status reporting behavior.

tests/test_ota_rollout.c


Documentation (7)
DEPENDENCIES.md Document OTA-related dependencies +10/-0

Document OTA-related dependencies

• Updates dependency documentation to include OTA cryptography/tooling components added in this PR.

DEPENDENCIES.md


README.md Document Tier-2 OTA behavior and setup +57/-2

Document Tier-2 OTA behavior and setup

• Updates the Arduino satellite documentation to describe OTA requirements, key provisioning, and high-level apply/rollback behavior.

dawn_satellite_arduino/README.md


ota_pubkey.h.example Provide example OTA public key header +29/-0

Provide example OTA public key header

• Adds an example device-side public keyring header used to verify Ed25519 signatures on manifests.

dawn_satellite_arduino/ota_pubkey.h.example


DAP2_SATELLITE.md Document firmware_version + OTA capability fields +73/-1

Document firmware_version + OTA capability fields

• Updates satellite/DAP2 documentation to include firmware_version reporting and the OTA capability advertised at registration.

docs/DAP2_SATELLITE.md


OTA_DESIGN.md Add end-to-end OTA design document +358/-0

Add end-to-end OTA design document

• Adds a comprehensive design doc covering trust model, manifest wire format, tokenized download gating, DB state machine, rollback strategies, and rollout phases.

docs/OTA_DESIGN.md


SATELLITE_OTA_DEB_TEST_PLAN.md Add Tier-1 OTA '.deb' test plan +109/-0

Add Tier-1 OTA '.deb' test plan

• Adds a dedicated test plan for validating Tier-1 '.deb' OTA update behavior, including rollback and safety checks.

docs/SATELLITE_OTA_DEB_TEST_PLAN.md


WEBSOCKET_PROTOCOL.md Document OTA WebSocket message types +44/-0

Document OTA WebSocket message types

• Extends the WebSocket protocol documentation with OTA messages (offer/status/ack/reject and admin controls).

docs/WEBSOCKET_PROTOCOL.md


Other (33)
ci.yml Add CI coverage for satellite and preset builds +73/-0

Add CI coverage for satellite and preset builds

• Introduces additional CI jobs/steps to compile satellite targets and multiple build presets, catching config-gated build breakage earlier.

.github/workflows/ci.yml


release.yml Add release workflow automation +71/-0

Add release workflow automation

• Adds a GitHub Actions workflow to build and/or publish release artifacts as part of the project’s release process.

.github/workflows/release.yml


CMakeLists.txt Refactor build wiring and link OTA core into daemon +51/-16

Refactor build wiring and link OTA core into daemon

• Moves always-needed sources out of feature-gated blocks, adds OTA core sources to the daemon build, and ensures libsodium is treated as always-required across presets.

CMakeLists.txt


DawnTools.cmake Adjust CMake tooling helpers +4/-2

Adjust CMake tooling helpers

• Small changes to internal CMake helpers to support the updated build matrix introduced alongside OTA work.

cmake/DawnTools.cmake


dawn.toml.example Add OTA configuration example settings +14/-0

Add OTA configuration example settings

• Extends the sample configuration with OTA-related fields (enablement and runtime settings).

dawn.toml.example


CMakeLists.txt Wire Tier-1 satellite OTA modules into build +30/-4

Wire Tier-1 satellite OTA modules into build

• Updates the Raspberry Pi satellite build to compile and link new OTA apply/rollback/WS support and shared manifest verification code.

dawn_satellite/CMakeLists.txt


Dockerfile.pibase Add Pi base image for reproducible builds +82/-0

Add Pi base image for reproducible builds

• Introduces a Dockerfile used as a base for building Raspberry Pi–compatible satellite artifacts.

dawn_satellite/Dockerfile.pibase


Dockerfile.pibuild Add Pi build container definition +38/-0

Add Pi build container definition

• Introduces a Dockerfile to build Pi-ABI satellite artifacts from the working tree in a controlled environment.

dawn_satellite/Dockerfile.pibuild


build-pi.sh Add helper script to build Pi artifacts via Docker +80/-0

Add helper script to build Pi artifacts via Docker

• Adds a script to drive the new Pi build containers and produce satellite binaries/packages for OTA staging/testing.

dawn_satellite/build-pi.sh


ota-release.sh Add OTA release staging script for Tier-1 artifacts +197/-0

Add OTA release staging script for Tier-1 artifacts

• Adds an operator script to stage signed OTA releases into the daemon’s release store and support push workflows.

dawn_satellite/ota-release.sh


package-deb.sh Add Debian packaging script for Tier-1 satellite +133/-0

Add Debian packaging script for Tier-1 satellite

• Introduces packaging automation to produce a '.deb' artifact suitable for Tier-1 OTA distribution.

dawn_satellite/package-deb.sh


dawn-satellite-fetch-models Add packaging helper for satellite assets/models +120/-0

Add packaging helper for satellite assets/models

• Adds a helper script used during packaging/install to fetch or prepare required satellite runtime assets.

dawn_satellite/packaging/dawn-satellite-fetch-models


conffiles Add Debian conffiles list +4/-0

Add Debian conffiles list

• Defines which satellite configuration files are treated as dpkg conffiles during install/upgrade.

dawn_satellite/packaging/debian/conffiles


control Add Debian control metadata +17/-0

Add Debian control metadata

• Defines the Debian package metadata and dependencies for the Tier-1 satellite package.

dawn_satellite/packaging/debian/control


postinst Add Debian post-install hooks +74/-0

Add Debian post-install hooks

• Adds post-install behavior needed by the Tier-1 package, including OTA-related cleanup/setup steps.

dawn_satellite/packaging/debian/postinst


postrm Add Debian post-remove hooks +30/-0

Add Debian post-remove hooks

• Adds cleanup logic executed after package removal for the Tier-1 satellite.

dawn_satellite/packaging/debian/postrm


prerm Add Debian pre-remove hooks +14/-0

Add Debian pre-remove hooks

• Adds logic executed before package removal/upgrade to support safe service handling around OTA updates.

dawn_satellite/packaging/debian/prerm


build-esp32.sh Add helper script for ESP32 builds +125/-0

Add helper script for ESP32 builds

• Adds a build script for producing ESP32 firmware artifacts used in Tier-2 OTA workflows.

dawn_satellite_arduino/build-esp32.sh


tweetnacl.c Vendor TweetNaCl for Ed25519 verification +809/-0

Vendor TweetNaCl for Ed25519 verification

• Adds vendored TweetNaCl sources used by ESP32 to verify Ed25519 signatures without introducing heavier crypto dependencies.

dawn_satellite_arduino/tweetnacl.c


tweetnacl.h Add TweetNaCl header +272/-0

Add TweetNaCl header

• Adds the corresponding TweetNaCl header required by the ESP32 OTA verification code.

dawn_satellite_arduino/tweetnacl.h


format_code.sh Update formatting script for new code paths +2/-0

Update formatting script for new code paths

• Minor updates to the formatting helper script to account for new/changed files introduced in this branch.

format_code.sh


auth_db_internal.h Update internal auth_db definitions for OTA schema evolution +1/-1

Update internal auth_db definitions for OTA schema evolution

• Adjusts internal auth_db header usage to support the new OTA schema migration path.

include/auth/auth_db_internal.h


dawn_config.h Add OTA configuration fields +11/-0

Add OTA configuration fields

• Extends the configuration schema with OTA settings used by the daemon (enablement, directories, TLS and token behavior).

include/config/dawn_config.h


version.h Align version definition with build single-source-of-truth +1/-1

Align version definition with build single-source-of-truth

• Adjusts version wiring so the version header remains authoritative and avoids conflicting build-time defines.

include/version.h


install-git-hooks.sh Update git hook installer +3/-2

Update git hook installer

• Updates the hook installation script to reflect changes in pre-push tooling/checks introduced by this branch.

install-git-hooks.sh


pre-push.hook Expand pre-push smoke/build coverage +69/-1

Expand pre-push smoke/build coverage

• Extends the pre-push hook to build additional presets/targets (including satellites) and run smoke checks before pushing.

pre-push.hook


dawn-satellite.conf Adjust satellite service configuration defaults +3/-2

Adjust satellite service configuration defaults

• Updates the satellite service configuration to align with new OTA behaviors and operational requirements.

services/dawn-satellite/dawn-satellite.conf


dawn-satellite.service Update systemd unit for satellite OTA/launcher behavior +15/-6

Update systemd unit for satellite OTA/launcher behavior

• Adjusts the systemd unit definition for the Tier-1 satellite to support the updated startup/launcher and OTA-related needs.

services/dawn-satellite/dawn-satellite.service


install.sh Update satellite install script for OTA packaging/runtime changes +100/-25

Update satellite install script for OTA packaging/runtime changes

• Updates installer logic to deploy and configure Tier-1 satellite components needed after the OTA and packaging changes.

services/dawn-satellite/install.sh


config_defaults.c Add defaults for OTA configuration +6/-0

Add defaults for OTA configuration

• Introduces default values for new OTA config fields.

src/config/config_defaults.c


config_env.c Add environment overrides for OTA configuration +7/-0

Add environment overrides for OTA configuration

• Adds environment-variable parsing for OTA configuration fields.

src/config/config_env.c


config_parser.c Parse OTA settings from TOML +15/-0

Parse OTA settings from TOML

• Extends the config parser to read OTA settings from the TOML configuration.

src/config/config_parser.c


config_validate.c Validate OTA configuration +8/-0

Validate OTA configuration

• Adds validation logic for OTA configuration values to fail fast on invalid settings.

src/config/config_validate.c


Grey Divider

Qodo Logo

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds an end-to-end, signed OTA update pipeline for both satellite tiers (RPi .deb and ESP32-S3), including server-side orchestration (single-device push + canary-gated fleet rollout), device-side apply/rollback logic, and operator surfaces (WebUI + dawn-admin) with CI/build hardening to keep the daemon buildable across presets.

Changes:

  • Introduces signed OTA release/offer primitives (manifest + token-gated HTTPS image fetch) plus per-device OTA state tracking and finalize-on-register semantics.
  • Adds fleet rollout orchestrator (canary → fan-out) and exposes OTA operations via WebUI + dawn-admin + WebSocket protocol updates.
  • Implements Tier-1 apply/rollback launcher + Debian packaging; adds Tier-2 OTA boot/apply path plus CI compile gates and tooling updates.

Reviewed changes

Copilot reviewed 101 out of 102 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
www/js/dawn.js Routes OTA WS responses/errors
www/css/components/satellites.css Styles OTA + fleet rollout UI
www/js/admin/satellites.js Admin OTA panel behaviors
tests/CMakeLists.txt Registers new OTA unit tests
tests/test_ota_rollout.c Rollout state machine tests
tests/test_ota_marker.c Marker codec tests
tests/test_ota_launch.c Launcher decision tests
src/webui/webui_satellite.c Records fw version + caps
src/webui/webui_message_dispatch.c Dispatches OTA WS messages
src/webui/webui_http.c Adds OTA image download route
src/webui/webui_admin_satellite.c Exposes OTA state in list
src/webui/webui_ota.c OTA WS transport + push
src/ui/tui.c Version header include
src/tools/tools_init.c WebUI-gates messaging tool
src/tools/phone_service.c WebUI-gates session/messaging hooks
src/tools/calendar_service.c Email-tool-gates OAuth revoke check
src/dawn.c OTA init + rollout tick
src/core/session_manager.c Prefer live session by UUID
src/core/scheduler.c WebUI-off weak fallback hooks
src/config/config_validate.c Validates [ota] config
src/config/config_parser.c Parses [ota] TOML
src/config/config_env.c Serializes [ota] to TOML
src/config/config_defaults.c Adds [ota] defaults
src/auth/auth_db_schema.c Adds OTA device state table/migrations
src/auth/admin_socket.c Adds OTA admin opcodes
src/auth/admin_socket_ota.c Implements OTA admin handlers
services/dawn-satellite/dawn-satellite.service Switches ExecStart to launcher
services/dawn-satellite/dawn-satellite.conf Debian-correct env file notes
pre-push.hook Builds satellite + preset smoke
install-git-hooks.sh Updates hook descriptions
include/webui/webui_ota.h Declares OTA WS transport API
include/version.h Bumps daemon version macro
include/core/session_manager.h Adds capabilities.ota
include/core/ota.h Declares OTA daemon API
include/core/ota_rollout.h Declares rollout orchestrator API
include/core/ota_manifest.h Declares signed manifest core
include/core/ota_db.h Declares OTA DB state API
include/config/dawn_config.h Adds ota_config_t
include/auth/auth_db_internal.h Bumps schema version to v60
include/auth/admin_socket.h Defines OTA admin opcodes/payloads
include/auth/admin_socket_internal.h Declares OTA admin handlers
format_code.sh Excludes vendored TweetNaCl from formatting
docs/WEBSOCKET_PROTOCOL.md Documents OTA WS messages
docs/SATELLITE_OTA_DEB_TEST_PLAN.md Adds Tier-1 OTA test plan
docs/DAP2_SATELLITE.md Adds .deb + OTA docs
DEPENDENCIES.md Documents TweetNaCl + arduino-cli
dawn.toml.example Adds [ota] example config
dawn-admin/socket_client.h Adds OTA client APIs
dawn-admin/socket_client.c Implements OTA client APIs
dawn-admin/main.c Adds dawn-admin ota … commands
dawn_satellite/CMakeLists.txt Adds OTA apply + launcher build
dawn_satellite/src/main.c Adds version marker + cleanup
dawn_satellite/src/ws_client.c Handles ota_offer + status flush
dawn_satellite/src/voice_processing.c Fixes headless cleanup gating
dawn_satellite/src/ui/sdl_ui.c Displays firmware version
dawn_satellite/src/ota_marker.c Adds marker codec implementation
dawn_satellite/src/ota_launch.c Adds launcher entrypoint
dawn_satellite/src/ota_launch_core.c Adds launcher decision core
dawn_satellite/include/ws_client.h Declares OTA WS sends
dawn_satellite/include/satellite_version.h Centralizes fw version
dawn_satellite/include/ota_marker.h Declares marker + paths
dawn_satellite/include/ota_launch.h Declares launcher API
dawn_satellite/include/ota_apply.h Declares Tier-1 apply API
dawn_satellite/include/ota_apply_internal.h Declares apply decision helpers
dawn_satellite/build-pi.sh Adds Pi build workflow
dawn_satellite/package-deb.sh Builds Tier-1 .deb
dawn_satellite/packaging/debian/control Debian package metadata
dawn_satellite/packaging/debian/conffiles Marks conffiles
dawn_satellite/packaging/debian/postinst Seeds live/rollback binaries
dawn_satellite/packaging/debian/prerm Stops service on remove/upgrade
dawn_satellite/packaging/debian/postrm Cleanup on remove/purge
dawn_satellite/packaging/dawn-satellite-fetch-models Operator model downloader
dawn_satellite/Dockerfile.pibuild Headless Pi build image
dawn_satellite/Dockerfile.pibase Full Pi base image (ML deps)
dawn_satellite_arduino/README.md Adds ESP32 OTA instructions
dawn_satellite_arduino/satellite_version.h Centralizes ESP32 fw version
dawn_satellite_arduino/partitions.csv Dual-OTA partition layout
dawn_satellite_arduino/ota_pubkey.h.example Example OTA pubkey header
dawn_satellite_arduino/ota_apply.h ESP32 OTA public API
dawn_satellite_arduino/dawn_satellite_arduino.ino OTA offer handling + boot path
dawn_satellite_arduino/build-esp32.sh CLI/CI ESP32 build gate
CMakeLists.txt Build refactor + ota-keytool target
cmake/DawnTools.cmake WebUI-gates webui_oauth source
.gitignore Ignores ESP32 OTA pubkey header
.github/workflows/ci.yml Adds satellite + esp32 compile jobs
.github/workflows/release.yml Adds arm64 Tier-1 release build

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/webui/webui_ota.c
Comment on lines +134 to +143
/* Require the device online before reserving an offer (avoids an orphaned
* in-flight row for an unreachable device). */
session_t *session = session_find_by_uuid(uuid);
if (!session || session->disconnected) {
if (session) {
session_release(session);
}
return AUTH_DB_NOT_FOUND;
}

Comment on lines +128 to +132
int handle_ota_push_cmd(int client_fd, const char *payload, uint16_t payload_len) {
/* Minimum: flags + uuid_len + 1 uuid byte + 1 version byte = 4. */
if (!payload || payload_len < 4) {
return send_text_response(client_fd, ADMIN_RESP_FAILURE, "Invalid ota push payload");
}
Comment on lines +1 to +6
#include "tweetnacl.h"
#define FOR(i,n) for (i = 0;i < n;++i)
#define sv static void

typedef unsigned char u8;
typedef unsigned long u32;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Action required

1. tweetnacl.c missing gpl header 📘 Rule violation § Compliance

The newly added files dawn_satellite_arduino/tweetnacl.c and dawn_satellite_arduino/tweetnacl.h
start immediately with code (includes/guards/macros) and omit the required GPLv3 header block at the
top. This can cause licensing/audit compliance failures for newly added C/C++ source and header
files.
Agent Prompt
## Issue description
The newly added files `dawn_satellite_arduino/tweetnacl.c` and `dawn_satellite_arduino/tweetnacl.h` are missing the mandated GPLv3 header block at the top of the file before any includes, macros, or header guards.

## Issue Context
Per PR Compliance ID 278939 and the repository’s standard GPLv3 header template (per CLAUDE.md), every new `.c`/`.h`/`.cpp` file must include the GPLv3 header at the top to avoid licensing/audit compliance issues.

## Fix Focus Areas
- dawn_satellite_arduino/tweetnacl.c[1-20]
- dawn_satellite_arduino/tweetnacl.h[1-25]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Intentional, not an omission. tweetnacl.{c,h} is vendored unmodified from upstream TweetNaCl (public domain), so we deliberately do not add the GPLv3 header — adding our license to third-party code would misrepresent it. It is attributed in NOTICE and DEPENDENCIES.md per the project's third-party-attribution policy, and excluded from format_code.sh. Leaving as-is.

Comment thread include/core/ota_rollout.h Outdated
Comment thread include/core/ota_rollout.h Outdated
Comment on lines +54 to +62
/**
* @brief Register the per-device delivery function (transport layer, Layer 4).
*
* Called once at init with webui_ota_push. Until set, ota_rollout_start() fails
* (no way to deliver an offer). Signature matches webui_ota_push():
* returns AUTH_DB_SUCCESS | AUTH_DB_LOCKED | AUTH_DB_NOT_FOUND | AUTH_DB_FAILURE.
*/
typedef int (*ota_rollout_push_fn)(const char *uuid, const char *version, bool allow_downgrade);
void ota_rollout_set_push_fn(ota_rollout_push_fn fn);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Action required

3. ota_rollout_set_push_fn() missing doxygen 📘 Rule violation ✧ Quality

The public API function ota_rollout_set_push_fn() is declared without an immediately preceding
Doxygen-style comment block with @param/@return tags. This breaks the requirement that all
public API functions be documented in a Doxygen-recognized format.
Agent Prompt
## Issue description
`ota_rollout_set_push_fn()` is a public header declaration and needs an immediately preceding Doxygen comment block, including `@param` for its parameters and `@return` if non-void.

## Issue Context
The existing Doxygen block is separated from the function declaration by a typedef, so documentation tools may not associate it with the function.

## Fix Focus Areas
- include/core/ota_rollout.h[54-63]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Comment on lines +171 to +173
/* External linkage (not static): ota_apply.cpp references it via `extern char
* persistentUUID[]` to build the download URL. */
char persistentUUID[37] = {0};

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Action required

4. persistentuuid not snake_case 📘 Rule violation ✧ Quality

The modified global identifier persistentUUID is not snake_case (contains uppercase letters). This
violates the naming convention for C/C++ identifiers in modified code.
Agent Prompt
## Issue description
The modified global variable `persistentUUID` violates the required snake_case naming convention.

## Issue Context
This identifier is newly modified (changed linkage) in this PR, so it falls under the rule scope for modified variables.

## Fix Focus Areas
- dawn_satellite_arduino/dawn_satellite_arduino.ino[171-173]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Skipping. This is the ESP32 Arduino sketch, which follows Arduino-ecosystem camelCase throughout (webSocket, nvsPrefs, loadOrCreateUUID, etc.). The snake_case convention in CLAUDE.md applies to the DAWN daemon C/C++ sources; renaming a single identifier here would make the sketch internally inconsistent. Leaving as-is.

Comment on lines +1217 to +1220
int ws_client_send_ota_ack(ws_client_t *client) {
if (!client) {
return -1;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Action required

5. ws_client_send_ota_ack() returns -1 📘 Rule violation ≡ Correctness

The new OTA websocket helpers return negative values (e.g., -1) to signal invalid
arguments/errors. This violates the project rule disallowing negative return values as an error
signaling mechanism.
Agent Prompt
## Issue description
New functions use `return -1;` as an error sentinel (e.g., when `client` is NULL). The project disallows negative return values for error signaling.

## Issue Context
These OTA send helpers are new API surface used by OTA code paths.

## Fix Focus Areas
- dawn_satellite/src/ws_client.c[1217-1247]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Skipping. The ws_client_send_ota_* helpers follow ws_client.c's established send-API convention — every ws_client_send_* returns -1 on error (documented in ws_client.h). Converting only the new OTA helpers to SUCCESS/FAILURE would make them inconsistent with every other sender in the file, and normalizing the whole API is out of scope for this PR. The no-negative-returns rule targets the daemon, not this localized satellite-client send API; filed as a follow-up if the send API is ever normalized.

Comment thread src/core/ota_db.c
The Docker server image excludes dawn_satellite/ (.dockerignore) while
add_subdirectory(tests) is unconditional, so `cmake --preset server` in the
dawn-builder stage failed its configure step on the missing satellite sources —
test_ota_marker/launch/apply compile dawn_satellite/src/*.c directly.

Guard those three registrations on the satellite tree being present. The
daemon-side OTA tests (test_ota, test_ota_rollout) don't reference the satellite
sources and stay enabled everywhere.

Test: cmake --preset debug configures clean; the three guarded tests still build
and pass locally; no unguarded dawn_satellite/ refs remain in the server configure
path, so the Docker (absent-tree) configure now succeeds.
…il, push gate)

From the Copilot + Qodo bot reviews.

- webui_ota_push now gates on the device's advertised DAP2 capabilities.ota
  before reserving an offer — offering to a non-OTA device could wedge the
  single-flight 'offered' row until token expiry (Copilot).
- handle_ota_push_cmd gates on ota_enabled() like its sibling commands, so a push
  under [ota].enabled=false returns a clear "OTA is disabled" instead of a
  misleading "no matching release" (Copilot).
- ota_finalize_on_register only auto-fails a pending target from genuinely
  in-progress states (downloading/verifying/applying/rebooting). It previously
  failed any state != 'offered', so a stale offer the startup reconcile rewrote to
  'unknown' was falsely marked failed when the device reconnected on the old
  version (Qodo bug). + regression test test_finalize_unknown_not_failed.
- ota_rollout.h: rename typedef ota_rollout_push_fn -> ota_rollout_push_fn_t and
  give ota_rollout_set_push_fn its own Doxygen block (Qodo nits).

Test: ctest -L ci 73/73; daemon + tests build clean; format clean.
@KerseyFabrications KerseyFabrications merged commit 0744549 into main Jun 11, 2026
5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants