diff --git a/docs/src/architecture.md b/docs/src/architecture.md index bfdb8c0..34b95a3 100644 --- a/docs/src/architecture.md +++ b/docs/src/architecture.md @@ -39,7 +39,7 @@ kaptan-config-mavlink ──► state-only ConfigProtocol ├── one ConfigManager ──► kaptan-config ──► flight schema/store └── one bounded MAVLink log-sink queue │ - kaptan-log-mavlink payload adapter + kaptan-diagnostics-mavlink payload adapter │ platform-owned sole encoder / DMA TX link ``` @@ -48,17 +48,17 @@ The generated dialect is intentionally custom rather than a `PARAM_*` or QGC adapter. The core uses numeric field IDs and typed scalar values; descriptor text is metadata, not a wire identity. Discovery/value reads, exact-index descriptor and enum-option requests, and structured results are custom-dialect -records. Runtime logs use one bounded text message with MAVLink severity and -flags. Remote writes use only schema-described typed scalar setters. The core +records. Runtime logs use one bounded text message with a conventional level +and flags. Remote writes use only schema-described typed scalar setters. The core owns one RAM transaction and the distinction between boot-active, committed, and editable candidate snapshots. In the current P1 policy a commit is restart-only. It cannot mutate controller or estimator state in flight. Target-owned stores may serialize one bounded complete CBOR snapshot. SITL -supplies the host file adapter and persists a private nonzero storage sequence -alongside that snapshot. The serialization and sequence are not configuration -state and are never exposed by the MAVLink protocol. +supplies the host file adapter and atomically replaces that snapshot through a +same-directory temporary file. The serialization is not configuration state +and is never exposed by the MAVLink protocol. Motor allocation/mixer topology is immutable compiled vehicle capability data, never a scalar field or CBOR candidate member. F722 now recovers operational settings from a generic two-slot journal before constructing consumers. Its @@ -74,18 +74,22 @@ option request. The first accepted transaction binds one source principal through a bounded post-terminal replay lease; foreign traffic cannot evict a lost result before that lease expires. -The MAVLink dialect root includes `kaptan/config.xml`, `kaptan/log.xml`, -`kaptan/error.xml`, and `kaptan/shutdown.xml`. +The MAVLink dialect root includes `kaptan/config.xml`, `kaptan/log.xml`, and +`kaptan/error.xml`. The build watches every fragment and generates one `message::kaptan` module; fragments are never separately generated Rust modules. `kaptan-log` itself has -no MAVLink, platform, time, allocation, filesystem, or configuration -dependency. Event creation is independent from sink filtering, allowing a -future blackbox and MAVLink to select different policies. Each sink owns -bounded Critical and BestEffort queues. The platform drains a MAVLink record -only after TX admission. `KAPTAN_LOG` carries -severity plus one bounded text field; `KAPTAN_ERROR` carries exact error -severity and flags plus bounded path and additional-information fields. Local saturation accounting remains -inside the sink queue, facade, and producer mailboxes; heartbeat and safety +no MAVLink, platform, time, allocation, or filesystem dependency; its sole +configuration coupling is compile-time enum metadata for `LevelFilter`. +`kaptan-err-propagate` independently owns explicitly assigned typed-error identity, +function-boundary propagation, and a bounded nonblocking ingress; it has +no logging, MAVLink, platform, filesystem, or clock dependency. Event creation +is independent from sink filtering, allowing a future blackbox and MAVLink to +select different policies. The platform drains typed errors before its bounded +critical and best-effort log queues and removes a pending record only after TX +admission. `KAPTAN_LOG` carries one conventional level plus one bounded text +field; `KAPTAN_ERROR` carries an explicitly assigned 16-bit ID and the bounded +`file:line` of the reporting boundary. Its +message kind already identifies a typed firmware error. Heartbeat and safety telemetry remain separate from diagnostics. See [Configuration](configuration.md) for the deployment matrix and GCS transaction diff --git a/docs/src/boards/geprc-f722-v2.md b/docs/src/boards/geprc-f722-v2.md index f672cea..529b4bb 100644 --- a/docs/src/boards/geprc-f722-v2.md +++ b/docs/src/boards/geprc-f722-v2.md @@ -7,7 +7,7 @@ labels the external UART2 pads `T2` and `R2`. Its board-local `UserConfig` factory selects endpoint `1/1`, raw-IMU and heartbeat publication rates, and an enabled MAVLink diagnostic sink with an -`INFO` maximum severity. The web configurator may replace those +`Info` level filter. The web configurator may replace those target-advertised values in the journal. Range and optical-flow fields are absent because this image has no vertical-sensor route. Its `ImmutableConfig` keeps the ICM-42688-P route, yaw-180° mount, diff --git a/docs/src/boards/kakute-f7-mini-v3.md b/docs/src/boards/kakute-f7-mini-v3.md index 8c99ca1..592cfaa 100644 --- a/docs/src/boards/kakute-f7-mini-v3.md +++ b/docs/src/boards/kakute-f7-mini-v3.md @@ -7,7 +7,7 @@ The checked-in manufacturer datasheet is Its board-local `UserConfig` factory selects endpoint `1/1`, raw-IMU, range, optical-flow and heartbeat publication rates, and an enabled MAVLink diagnostic -sink with an `INFO` maximum severity. The web configurator may replace those +sink with an `Info` level filter. The web configurator may replace those target-advertised values in the journal. Its `ImmutableConfig` keeps the MPU-6000 route, body-aligned mount and concrete driver setup, USART1 pins and baud, MTF01 route and baud, diff --git a/docs/src/configuration.md b/docs/src/configuration.md index df25067..7cf053e 100644 --- a/docs/src/configuration.md +++ b/docs/src/configuration.md @@ -47,15 +47,18 @@ The Incoming MAVLink table at the bottom groups live, CRC-valid packets by source and message ID and shows the measured receive frequency and total count. Names come from the generated MAVLink dialect decoders; the browser contains no message catalog. Selecting a message name shows the latest decoded field values; -the diagnostic projection renders `KAPTAN_ERROR` path and additional information -as text. The configurator keeps only one latest value per bounded aggregate, +the diagnostic projection renders the `KAPTAN_ERROR` ID and reporting +`file:line`. The +configurator keeps only one latest value per bounded aggregate, rather than packet history, and clears it for every serial-link session. The separate Runtime logs section retains the most recent 512 `KAPTAN_LOG` and `KAPTAN_ERROR` events in receive order. Each row shows local receive time, -decoded severity, and readable diagnostic text. Repeated events remain separate; +decoded level, and readable diagnostic text. Repeated events remain separate; older host-side entries are discarded at the fixed display bound and the UI -reports that count. This event history is independent of the packet table's +reports that count. A visible `TRUNCATED` badge marks bounded `KAPTAN_LOG` +text. This +event history is independent of the packet table's single latest-value projection and resets when the serial link changes. Each F722 board owns the exact settings consumed by its compiled image. Both @@ -63,7 +66,7 @@ current boards advertise: - MAVLink system and component identity; - raw-IMU and heartbeat publication rates; and -- whether the MAVLink diagnostic sink is enabled and its maximum severity. +- the MAVLink diagnostic sink's Off/Error/Warn/Info/Debug/Trace level filter. The Kakute image also advertises range and optical-flow publication rates because its immutable route includes an MTF01. The GEPRC image declares no @@ -105,7 +108,7 @@ controller, safety supervisor, allocator envelope, or MAVLink diagnostic sink: - mass, collective-thrust, and tilt limits; - rate PID gains and limits; - actuator minimum, maximum, and slew limits; and -- the restart-only MAVLink diagnostic-sink enable and maximum severity. +- the restart-only MAVLink diagnostic-sink level filter. The fixed Quad-X mixer is intentionally not configurable. It is reviewed capability data, not a user-editable array. diff --git a/docs/src/errors.md b/docs/src/errors.md index feaf193..3214180 100644 --- a/docs/src/errors.md +++ b/docs/src/errors.md @@ -3,49 +3,86 @@ Public Kaptan error enums derive the pure `KaptanError` implementation: ```rust -#[derive(Debug, kaptan_log::KaptanError)] +#[derive(Debug, kaptan_err_propagate::KaptanError)] enum SensorError { + #[kaptan_error(id = 12345)] TransferFault, + #[kaptan_error(id = 12346)] UnexpectedIdentity(u8), } + +#[kaptan_err_propagate::propagate] +fn service_sensor() -> Result<(), SensorError> { + read_sensor()?; + Ok(()) +} ``` The derive implements `core::error::Error`, `Display`, and `ErrorReport`. It -creates a static `module::Type::Variant` error path without allocating, -publishing telemetry, exposing generic source values, or changing safety -state. Renaming a module, type, or variant changes that path; it is not a -stable machine protocol identity. +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. -Normal-task code may use `log::error!("{error}")`; it is routed as a bounded -`KAPTAN_ERROR`. ISR-adjacent flight paths may enqueue the derived static -message through their existing bounded mailbox. Neither route is a replacement -for safety state, actuator inhibit, or typed configuration results. +`#[kaptan_err_propagate::propagate]` observes the ordinary `Result` produced +by one terminal diagnostic boundary. If an `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 annotated boundary. 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 terminal boundary is annotated. If nested functions are both +annotated, each boundary 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 +`KAPTAN_LOG`. Error propagation is not a replacement for safety state, +actuator inhibit, or typed configuration results. + +`kaptan-err-propagate` owns error identity and its independent one-slot +ingress. A pending record contains only the compact ID and a static reference +to the propagation boundary's `file:line`. The MAVLink adapter copies that +location into its fixed field when constructing `KAPTAN_ERROR`. Producers never +encode or transmit MAVLink themselves. Admission is bounded and lossy if a +different typed error is already pending. ## What the receiver sees - Configuration writes receive authoritative structured failures through `KAPTAN_CONFIG_RESULT`: operation, lifecycle state, field IDs, validation - rule, and protocol error code. -- Runtime driver and link faults reach the F722 MAVLink worker as a - `KAPTAN_ERROR` path, subject to bounded transport admission. The error - payload is `{ level, flags, path_length, additional_information_length, - path[96], additional_information[64] }`; `level` preserves the originating - MAVLink-compatible severity from Emergency through Error, each length - identifies the valid UTF-8 prefix, and the optional information field may be - empty. A `KaptanError` derive supplies the type/variant path and normally - leaves additional information empty. `log::error!` uses the Rust log target - as the path and the formatted message as additional information. -- A rejected configuration operation may enqueue a companion `KAPTAN_ERROR` - whose path is the high-level `ProtocolError` identity. It is intentionally - not correlated and carries no field/rule detail; the matching - `KAPTAN_CONFIG_RESULT` remains the sole precise authority. + rule, and protocol error code. A terminal persistence failure also reports + the storage-only `CommitTransactionError::Store` boundary once as + `KAPTAN_ERROR`; replaying the cached command result does not repeat the error + or the flash operation. +- Terminal typed `ErrorReport` faults reach the F722 MAVLink worker as a + `KAPTAN_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 annotated propagation boundary. + The message kind itself identifies an error; no level is duplicated in the + payload. Dynamic human-readable context belongs in `KAPTAN_LOG`, while + structured protocol failures remain in their owning response message. - Control, navigation, safety, and simulator error types are derive-ready but their live owners are not yet attached to a MAVLink worker. -- `KAPTAN_SHUTDOWN`, defined in `definitions/kaptan/shutdown.xml`, represents - only a future completed orderly, - output-inhibited shutdown. It is not a shutdown request, panic, hard-fault, - reset, or retention-record protocol. F722 still uses `panic-halt`; it does - not claim a live panic frame or retained-record replay. + +F722 still uses `panic-halt`; it does not claim a live panic frame 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 diff --git a/docs/src/logs.md b/docs/src/logs.md index ab5878f..c7beb73 100644 --- a/docs/src/logs.md +++ b/docs/src/logs.md @@ -1,94 +1,85 @@ # Logs -Normal host/SITL task code can use the standard facade: +Kaptan code uses the familiar logging macros on both host and flight targets: ```rust -log::debug!("estimator waiting for IMU"); -log::info!("configuration loaded"); -log::warn!("range aid stale"); -log::error!("sensor transfer failed"); // emitted as KAPTAN_ERROR +kaptan_log::debug!("estimator waiting for IMU"); +kaptan_log::info!("configuration loaded"); +kaptan_log::warn!("range aid stale"); +kaptan_log::error!("sensor transfer failed"); +kaptan_log::trace!("prediction step complete"); ``` +All five macros emit `KAPTAN_LOG`; their level never selects a different wire +message. When a terminal typed firmware error escapes a reviewed +`#[kaptan_err_propagate::propagate]` function, it makes one bounded admission attempt +to the separate `KAPTAN_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 text, error paths, and error details -use distinct 160-, 96-, and 64-byte storage types, so a queued record retains -exactly the common diagnostic-envelope capacity and needs no adapter-side -clipping buffer. F722 deliberately does not -link this facade in ISR, DMA, control, actuator, panic, hard-fault, or shutdown -contexts; those paths use static fault mailboxes that the worker turns into -messages later. +drains its bounded sink queue. Ordinary log text uses one 160-byte bounded +record. Typed-error records contain the explicit ID and a static reference to +the annotated propagation boundary until the MAVLink adapter copies its +`file:line` into the 96-byte `raised_at` field. +Logs and typed errors use independent fixed atomic +ingresses; interrupt and worker producers make one nonblocking admission +attempt, and only the MAVLink worker encodes or transmits a retained event. +Panic and hard-fault paths still do not attempt live telemetry. ## Receiver contract -`definitions/kaptan/log.xml`, `definitions/kaptan/error.xml`, and -`definitions/kaptan/shutdown.xml` separate ordinary logs, errors, and -orderly-shutdown observations: +`definitions/kaptan/log.xml` and `definitions/kaptan/error.xml` separate +ordinary logs and typed errors: ```text KAPTAN_LOG { level, flags, message_length, message[160] } KAPTAN_ERROR { - level, flags, path_length, additional_information_length, - path[96], additional_information[64] + error_id, raised_at[96] } -KAPTAN_SHUTDOWN { reason } ``` A GCS decoder generated from the same `kaptan.xml` revision must validate -`message_length <= 160` for `KAPTAN_LOG`, and `path_length <= 96` plus -`additional_information_length <= 64` for `KAPTAN_ERROR`, before slicing the -fixed arrays into UTF-8 prefixes. Generated MAVLink framing alone does not -enforce these semantic length fields. `KAPTAN_LOG` displays its text at the -MAVLink severity in `level`; `TRACE` is encoded as `DEBUG` plus the `TRACE` -flag. -`KAPTAN_ERROR.level` preserves the originating MAVLink-compatible severity from -Emergency through Error. The error channel accepts only `TRUNCATED` and -`COALESCED`; a `TRACE` bit is never emitted there. Unknown future flag bits -must be ignored. `COALESCED` means that one or more later observations from -the same source mailbox were folded while the retained first error was pending; -it does not claim that their paths or details were identical. +`message_length <= 160` for `KAPTAN_LOG`. `KAPTAN_ERROR.raised_at` is a +zero-padded UTF-8 `file:line` prefix; receivers use bytes before the first zero, +or the complete field when no zero is present. +`KAPTAN_LOG.level` accepts only Error (3), Warning (4), Info (6), and Debug +(7); Trace is encoded as Debug (7) plus the `TRACE` flag. Other MAVLink +severity numbers are not Kaptan log levels. +The `KAPTAN_ERROR` message kind already means a typed firmware error, so it has +no redundant level or flags field. -The derived error path is intentionally not a numeric error catalog; renaming -its Rust module/type/variant changes that identity. Error details requiring +`error_id` is the stable nonzero catalog number explicitly assigned to the +Rust error variant. Renaming or reordering a variant does not change it. +`raised_at` identifies the annotated boundary where the `Err` entered +diagnostics. Error details requiring machine action stay in their owning protocol, notably `KAPTAN_CONFIG_RESULT`. -`KAPTAN_SHUTDOWN` has only its typed reason and is valid only after an orderly -output-inhibited completion. Delivery is bounded and lossy under link pressure; -heartbeat and safety telemetry use separate paths. Logs have no publication -rate setting and are not periodic streams. A retained event leaves the -MAVLink sink queue once the lower transport admits it; temporary queue pressure -only defers that same event. +Delivery is bounded and lossy under link pressure; heartbeat and safety +telemetry use separate paths. Logs have no publication rate setting and are +not periodic streams. A retained event leaves its MAVLink sink only once the +lower transport admits it; temporary queue pressure only defers that same +event. ## Filtering -The restart-only `mavlink.logging.enabled` field controls the MAVLink -diagnostic sink independently from future sinks such as a blackbox. -`mavlink.logging.maximum_severity` accepts the complete MAVLink severity range, -Emergency (0) through Debug (7). For example, selecting Warning permits warning -and more severe events while suppressing notice, info, and debug. Disabling the -sink suppresses both `KAPTAN_LOG` and `KAPTAN_ERROR` on MAVLink without -suppressing events from another sink. F722 uses the validated policy recovered -at boot, falling back to its board factory when the journal is empty or -invalid. +The single restart-only `mavlink.logging.level_filter` field uses the familiar +Off, Error, Warn, Info, Debug, and Trace choices. `Info`, for example, accepts +Error, Warn, and Info log events. `Off` suppresses `KAPTAN_LOG` on MAVLink +without suppressing typed `KAPTAN_ERROR` reports or events sent to another +sink. There is no separate enable flag and no MAVLink-specific set of extra +runtime log levels. F722 uses the validated filter recovered at boot, falling +back to its board factory when the journal is empty or invalid. -Severity remains a typed `Severity` value inside the board and runtime. Generic -schema discovery exposes its named choices one at a time to the configurator, -so the UI does not carry a field-key-specific severity list; numeric conversion -exists only at the generic configuration transport and snapshot boundary. The MAVLink `level` -field intentionally remains `uint8_t`: this generator represents XML enums as -four-byte Rust values, so attaching an XML enum would widen the generated -payload type. The adapter performs the checked one-byte conversion at that -unavoidable wire boundary instead. +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. +Only the MAVLink adapter converts the five event levels to MAVLink's numeric +severity representation; Trace uses MAVLink Debug plus the Trace flag. -Cargo `log/max_level_*` features compile calls away before the facade sees -them; they are not a supported filtering mechanism. The build policy keeps -that decision out of workspace dependencies. - -The SITL facade ingress is transport-neutral and unfiltered. The tested +The log ingress is transport-neutral and unfiltered. The tested `SitlConfigRuntime` composition applies the configured MAVLink policy when -fanning an event into that sink and proves `log!` reaches a generated -`KAPTAN_LOG` payload, and rejected configuration replies enqueue a generated -`KAPTAN_ERROR` companion without replacing their authoritative result. +fanning an event into that sink and proves `kaptan_log::info!` reaches a +generated `KAPTAN_LOG` payload. `kaptan-sitl serve` does not yet attach a config/diagnostic MAVLink link. F722 images accept only the unsigned configuration service on their directly wired local UART; that direct-wire assumption does not extend to a shared or remote diff --git a/docs/src/mavlink.md b/docs/src/mavlink.md index e8e89ef..28a81d3 100644 --- a/docs/src/mavlink.md +++ b/docs/src/mavlink.md @@ -43,17 +43,16 @@ grants no additional command authority. Diagnostic events also feed a separate bounded Runtime logs view at `http://127.0.0.1:4861`. That view retains individual `KAPTAN_LOG` and -`KAPTAN_ERROR` events in receive order with local receive time, severity, and +`KAPTAN_ERROR` events in receive order with local receive time, level, and readable text. Packet inspection remains a latest-value view; it is not used as log history. The custom Kaptan definitions are aggregated by `definitions/kaptan.xml` from -`kaptan/config.xml`, `kaptan/log.xml`, `kaptan/error.xml`, and -`kaptan/shutdown.xml`. +`kaptan/config.xml`, `kaptan/log.xml`, and `kaptan/error.xml`. `KAPTAN_LOG` is an outbound human-readable four-field record. `KAPTAN_ERROR` -is its separate bounded error channel and preserves the originating error -severity, while `KAPTAN_SHUTDOWN` represents only -a completed orderly output-inhibited shutdown. Configuration messages back the -current target-described web configurator and the future signed GCS. See +is the separate bounded channel for typed firmware errors, carrying a stable +explicit ID plus the `file:line` of its propagation boundary, and needs no +redundant level field. Configuration messages back the current target-described +web configurator and the future signed GCS. See [Configuration](configuration.md), [Logs](logs.md), and [Errors](errors.md) for the exact contracts. diff --git a/docs/src/reference.md b/docs/src/reference.md index 6453ddd..28ce6fe 100644 --- a/docs/src/reference.md +++ b/docs/src/reference.md @@ -8,7 +8,7 @@ This page collects stable limits and points to the detailed contracts. | F722 persistence | One bounded fixed scalar snapshot inside each slot of a power-loss-safe two-slot flash journal. | | Config transaction | One owner-bound RAM transaction with typed scalar writes; commit is restart-required. | | Config discovery | Correlated `KAPTAN_CONFIG_INFO_REQUEST` / `KAPTAN_CONFIG_INFO`, then exact-index descriptor/enum-option and field-value requests. | -| Diagnostics | `KAPTAN_LOG` (51020) for ordinary severity/text; `KAPTAN_ERROR` (51021) for a bounded `{ level, path[96], additional_information[64] }` error; and `KAPTAN_SHUTDOWN` (51022) for completed orderly output-inhibited shutdowns. | +| Diagnostics | `KAPTAN_LOG` (51020) for macro-produced level/text; `KAPTAN_ERROR` (51021) for a bounded typed `{ error_id, raised_at[96] }` firmware error. | | F722 configuration | Target-described fields and board factory defaults; the current local transport is a directly wired unsigned UART with motor outputs physically inhibited. | Read [Configuration](configuration.md) for the field/consumer matrix and GCS