[AZ-558] Route C8 outbound encoder bytes through MavlinkTransport seam

All FC adapter outbound MAVLink bytes now go through the AZ-401
MavlinkTransport seam (NoopMavlinkTransport in replay,
SerialMavlinkTransport in live). New helpers in
_outbound_mavlink_payloads.py extract encode/pack/seq-bump so the four
AP _send sites and the iNav statustext _send site become
encode -> pack -> transport.write. TlogReplayFcAdapter emits real
AP-shape MAVLink bytes through the injected NoopMavlinkTransport,
satisfying replay protocol Invariant 5 and unblocking AZ-401 AC-9.

Closes AZ-558. Also unskips AZ-401 AC-9 and AZ-404 AC-4b. Live wire
output remains byte-identical (proven via two-instance MAVLink
byte-equivalence tests). AST scan asserts no .mav.<name>_send( calls
remain in the retrofit set (AP / iNav / tlog adapters).

Out of scope (logged in review): GCS adapter retrofit; airborne live
strategy registration that would activate the SerialMavlinkTransport
factory injection path.

Tests: 2110 passed, 92 environmental skips, 1 unrelated pre-existing
macOS cold-start flake deselected.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Oleksandr Bezdieniezhnykh
2026-05-16 05:33:45 +03:00
parent d7e6b0959e
commit 2b19b8b90b
18 changed files with 1106 additions and 90 deletions
@@ -131,6 +131,7 @@ class ReplayInputAdapter:
"_tlog_source_factory",
"_video_frames_factory",
"_video_timestamps_factory",
"_mavlink_transport",
"_log",
"_opened",
"_closed",
@@ -152,6 +153,7 @@ class ReplayInputAdapter:
tlog_source_factory: Any | None = None,
video_frames_factory: Any | None = None,
video_timestamps_factory: Any | None = None,
mavlink_transport: Any | None = None,
) -> None:
if not isinstance(video_path, Path):
raise ReplayInputAdapterError(
@@ -182,6 +184,7 @@ class ReplayInputAdapter:
self._tlog_source_factory = tlog_source_factory
self._video_frames_factory = video_frames_factory
self._video_timestamps_factory = video_timestamps_factory
self._mavlink_transport = mavlink_transport
self._log = logging.getLogger("replay_input.tlog_video_adapter")
self._opened = False
self._closed = False
@@ -268,6 +271,7 @@ class ReplayInputAdapter:
time_offset_ms=resolved_offset_ms,
pace=self._pace,
source_factory=self._tlog_source_factory,
mavlink_transport=self._mavlink_transport,
)
fc_adapter.open()
except (FcOpenError, FcAdapterConfigError, FcAdapterError) as exc: