fix(diagnostics): bound formatting and release ingress before callbacks

Bound UTF-8 formatting and release diagnostic ingress before invoking
callbacks. Check error-ID collisions at Rust/C++ release final link.

Validation: diagnostic tests, strict Clippy, Miri and final-link cases.
This commit is contained in:
Onur Özkan committed 2026-11-07 18:12:11 +03:00
1 parent 3226ef97fe
commit 84807dde4e
12 files changed
+547 -48

No files matched your search

+12 -7
View File
@@ -23,8 +23,15 @@ requires a nonzero explicit ID on every variant and retains no type, variant,
or module string. `Display` renders the decimal ID. Renaming, moving, or
reordering a Rust variant therefore does not change its receiver contract.
Missing and same-enum duplicate IDs are rejected by the derive. Reusing an ID
across error enums generates the same reserved linker symbol and fails the
final firmware build.
across error enums generates the same reserved symbol. Collision detection
requires both defining objects to participate in compilation or final linking.
It does not inspect unextracted archive members or prove catalog-wide uniqueness.
Stable ID allocation still requires review.
The [final-link fixture](../../firmware/libs/kaptan-err-reportable-macros/tests/final_link.md)
checks Rust 1.90.0 release fat-LTO and native Linux static-library links.
Run it with `make -C firmware native-test`. Each run retains its results and
Rust compiler identity in `results.txt`.
`#[kaptan_err_reportable::propagate]` turns an annotated function into a
reporting point. If a reportable `Err` leaves that function, the attribute makes
@@ -76,16 +83,14 @@ different reportable error is already pending.
`ERROR`, subject to bounded transport admission. The payload is
`{ error_id, raised_at[96] }`. `raised_at` is a zero-padded UTF-8
`file:line` prefix for the reporting point.
The message kind itself identifies an error; no level is duplicated in the
payload. Dynamic human-readable context belongs in `LOG`, while
Dynamic human-readable context belongs in `LOG`, while
structured protocol failures remain in their owning response message.
- Control, navigation, and safety error types are derive-ready but
their live owners are not yet attached to a MAVLink worker.
F722 still uses `panic-halt`; it does not claim a live panic frame or
retained-record replay.
F722 uses `panic-halt`, without live panic reporting or retained-record replay.
No diagnostic message may synchronously transmit from a panic handler, hard
fault, ISR, DMA callback, or motor-inhibit path. A later target integration
must inhibit outputs first, persist a validated crash record, then report it
only after the next boot’s static board telemetry is available.
only after MAVLink is available on the next boot.
+16 -12
View File
@@ -1,6 +1,6 @@
# Logs
Kaptan code uses the familiar logging macros on both host and flight targets:
Logging macros work on host and flight targets:
```rust,ignore
kaptan_log::debug!("estimator waiting for IMU");
@@ -11,16 +11,22 @@ kaptan_log::trace!("prediction step complete");
```
All five macros emit `LOG`; their level never selects a different wire
message. When a reportable firmware error escapes a reviewed
message. When a reportable firmware error escapes a
`#[kaptan_err_reportable::propagate]` function, it makes one bounded admission attempt
to the separate `ERROR` ingress.
`kaptan-log` formats each event once into a fixed bounded buffer and never
sends MAVLink, allocates, blocks, changes flight state, or touches DMA. The
composition fans the event into each enabled sink; the sole MAVLink worker
drains its bounded sink queue. Ordinary log text uses one 160-byte bounded
record. Reportable-error records contain the explicit ID and a static reference
to the reporting point until the MAVLink adapter copies its
`kaptan-log` formats each event once into fixed 160-byte UTF-8 storage. Its core
storage and admission paths use no allocation or blocking, and do not send
MAVLink, change flight state or touch DMA. Formatting invokes caller-supplied
`Display` implementations, which may allocate, block, ignore `fmt::Error` or do
unbounded work. Overflow stops formatters that propagate the error and latches
truncation even if a formatter ignores it. Bounded storage is not a universal
worst-case execution-time guarantee. Producers must choose formatters suitable
for their execution context.
The composition fans the event into each enabled sink. The MAVLink worker drains
its bounded sink queue. Reportable-error records contain the explicit ID and a
static reference to the reporting point until the MAVLink adapter copies its
`file:line` into the 96-byte `raised_at` field.
Logs and reportable errors use independent fixed atomic
ingresses; interrupt and worker producers make one nonblocking admission
@@ -47,8 +53,6 @@ or the complete field when no zero is present.
(2), Info (3), Warn (4), or Error (5). Off (0) is a filter-only sentinel and
is never emitted as a `LOG` event. `LOG.flags` currently owns only the
Truncated bit (1).
The `ERROR` message kind already means a reportable firmware error, so it has
no redundant level or flags field.
`error_id` is the stable nonzero catalog number explicitly assigned to the
Rust error variant. Renaming or reordering a variant does not change it.
@@ -73,8 +77,8 @@ runtime log levels. F722 uses the validated filter recovered at boot, falling
back to its board factory when the journal is empty or invalid.
Events retain the compact typed `Level` and sink configuration retains the
compact typed `LevelFilter`. Generic schema discovery exposes the filter's
named choices to the configurator, so the UI carries no hardcoded field list.
compact typed `LevelFilter`. Schema metadata supplies the filter choices to the
configurator.
Only the MAVLink adapter converts the five event levels to the typed Kaptan
wire enum.