[AZ-393] [AZ-394] [AZ-395] C8 outbound chain + AP MAVLink2 signing

AZ-393 ArduPilot outbound: PymavlinkArdupilotAdapter encodes
EstimatorOutput to MAVLink2 GPS_INPUT via gps_input_send; emits
NAMED_VALUE_FLOAT(name="src_lbl") every frame and STATUSTEXT on
source_label transition (1 Hz per-severity cap). Smoothed-output
guard (Invariant 6), single-writer thread (Invariant 8), SPD
propagation. Shared helper _outbound_provenance.py owns the
canonical source-label-to-float table + transition rate-limiter.

AZ-394 iNav outbound: Msp2InavAdapter encodes EstimatorOutput to
hand-rolled MSP2_SENSOR_GPS (0x1F03, 52-byte LE payload via
_msp2_sensor_gps_encoder.py + YAMSPy send_RAW_msg). Secondary
unsigned MAVLink channel for STATUSTEXT transitions. open()
rejects non-None signing_key (RESTRICT-COMM-2 / Invariant 2);
request_source_set_switch raises SourceSetSwitchNotSupportedError
(Invariant 9 verified: never calls setup_signing on secondary).

AZ-395 AP MAVLink2 signing: ephemeral per-flight 32-byte key
from secrets.token_bytes; pymavlink setup_signing handshake at
open(); in-place bytearray zeroisation on close(); mid-flight
signing-failure detection (ERROR log + WARNING STATUSTEXT + no
raise; threshold configurable). Key never logged / persisted /
serialised (regex-scanned by AC-4/AC-5). BUILD_DEV_STATIC_KEY=ON
enables repeatable static-key dev path; rejected at open() when
the build flag is absent.

Shared: EstimatorOutput.smoothed (default False) added for the
Invariant 6 gate at the C8 boundary; FcConfig extended with
dev_static_signing_key + signing_failure_threshold (additive
defaults; cross-field validation in __post_init__).

Tests: 33 new AC tests (11 + 11 + 11) covering all 30 ACs; full
suite 476 passing / 2 skipped / 0 failing (was 443). Contract
surfaces unchanged at fc_adapter_protocol v1.0.0 and
composition_root v1.2.0.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Oleksandr Bezdieniezhnykh
2026-05-11 04:47:44 +03:00
parent a61d2d3f4b
commit 1e0be08e8a
16 changed files with 2198 additions and 4 deletions
+4
View File
@@ -56,6 +56,8 @@ ENV_KEY_MAP: Final[dict[str, tuple[str, str]]] = {
"FC_PORT_DEVICE": ("fc", "port_device"),
"FC_PORT_BAUD": ("fc", "port_baud"),
"FC_SIGNING_KEY_SOURCE": ("fc", "signing_key_source"),
"FC_DEV_STATIC_SIGNING_KEY": ("fc", "dev_static_signing_key"),
"FC_SIGNING_FAILURE_THRESHOLD": ("fc", "signing_failure_threshold"),
"GCS_ADAPTER": ("gcs", "adapter"),
"GCS_PORT_DEVICE": ("gcs", "port_device"),
"GCS_PORT_BAUD": ("gcs", "port_baud"),
@@ -97,6 +99,8 @@ _FIELD_COERCIONS: Final[dict[str, type]] = {
"port_device": str,
"port_baud": int,
"signing_key_source": str,
"dev_static_signing_key": str,
"signing_failure_threshold": int,
"summary_rate_hz": float,
}