Validate configuration through its owning schemas and keep board declarations separate from shared F722 resource ownership. Bound USB transitions, isolate transport failures and retain restart-only activation. Migrate navigation and flight consumers with the sensor and schema contracts. Require explicit installation data and synchronous output authority; board motor outputs remain inhibited. Validation: assembled-prefix host tests, strict Clippy, platform/USB tests and both ARM release builds. Final-source coverage and scoped Miri/Kani evidence are retained separately from hardware qualification.
5.5 KiB
Errors
Public reportable Kaptan error enums derive KaptanError:
#[derive(Debug, kaptan_err_reportable::KaptanError)]
enum SensorError {
#[kaptan_error(id = 12345)]
TransferFault,
#[kaptan_error(id = 12346)]
UnexpectedIdentity(u8),
}
#[kaptan_err_reportable::propagate]
fn service_sensor() -> Result<(), SensorError> {
read_sensor()?;
Ok(())
}
The derive implements core::error::Error, Display, and ReportableError. It
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 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
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
one nonblocking admission attempt and returns the exact same Err to the
caller. The record contains the final converted error ID and the compile-time
file:line of that reporting point. This is where the Err enters
diagnostics, not necessarily the line where a deeper callee originally
constructed it. ?, explicit returns, result aliases, and From conversion
retain their normal Rust behavior. An error handled inside the function is
silent.
Stable IDs
Production error IDs are plain decimal numbers from 1 through 59999.
Values from 60000 through 65535 are reserved for tests, and 0 is
invalid. Once issued, an ID is never renumbered or reused, even if its error
variant is removed. Adding a variant therefore requires allocating a new ID;
renaming, moving, or reordering an existing variant keeps its assigned ID.
The example numbers above are illustrative and must be replaced with
unallocated catalog values when used in production code.
Only the intended reporting point is annotated. If nested functions are both
annotated, each function makes its own report attempt; the ingress does not
mark or mutate error values to deduplicate them. Expected validation,
backpressure, retry, and untrusted-input results remain unannotated. In
particular, transient SPI/I²C Busy and unavailable range measurements stay
inside their service retry/idle paths. In contrast, every logging
macro—including kaptan_log::error!—produces only
LOG. Error reporting is not a replacement for safety state,
actuator inhibit, or typed configuration results.
kaptan-err-reportable owns reportable-error identity and its independent
one-slot ingress. A pending record contains only the compact ID and a static
reference to the reporting point's file:line. The MAVLink adapter copies that
location into its fixed field when constructing ERROR. Producers never
encode or transmit MAVLink themselves. Admission is bounded and lossy if a
different reportable error is already pending.
What the receiver sees
- A rejected KCFG upload receives an authoritative
FILE_CONTROLstatus and bounded readable detail. Invalid user content is not a firmware error. A durable-storage failure additionally emits one stablePersistConfigurationErrorID that distinguishes journal geometry, model, sequence, flash-controller, bounds, unlock, and verification failures. Replaying the cached close result does not repeat the report or flash operation. Any persistence error makes committed state unknown and blocks ordinary saves until reboot recovery or a successful explicit factory reset. Boot-active values remain unchanged. - A stale KCFG expected base returns
REQUEST_CONFLICTbefore persistence. It is a reload-required editing conflict, not a reportable firmware error. Invalid boot configuration uses the UART-disabled USB setup path and retains readable saved values for repair. See Configuration. - SPI completion errors never copy partial data into the caller's receive
buffer. If bounded shutdown cannot establish quiescence, the SPI route
quarantines its static DMA storage and refuses further transfers. This is
distinct from transient
Busy. Physical SPI shutdown and fault behavior remain hardware-validation work. - Reported
ReportableErrorvalues reach the F722 MAVLink worker as aERROR, subject to bounded transport admission. The payload is{ error_id, raised_at[96] }.raised_atis a zero-padded UTF-8file:lineprefix for the reporting point. Dynamic human-readable context belongs inLOG, 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 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 MAVLink is available on the next boot.