Files
kaptan/docs/src/errors.md
T
Onur Özkan f9a61a086f feat(firmware): validate configuration and migrate sensor-driven flight APIs
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.
2026-11-07 18:12:11 +03:00

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_CONTROL status and bounded readable detail. Invalid user content is not a firmware error. A durable-storage failure additionally emits one stable PersistConfigurationError ID 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_CONFLICT before 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 ReportableError values reach the F722 MAVLink worker as a 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. 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 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.