Oleksandr Bezdieniezhnykh
2b19b8b90b
[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 >
2026-05-16 05:33:56 +03:00
Oleksandr Bezdieniezhnykh
823c0f1b2e
[AZ-398] Replay: FrameSource + Clock Protocols + Clock injection
...
Ship the two Layer-1 cross-cutting Protocols replay mode needs to leave
production C1-C5 components mode-agnostic (Invariant 1) and replay-
deterministic (Invariant 2). Live + replay binaries see the same
interfaces; only the strategy differs.
* Clock Protocol (monotonic_ns / time_ns / sleep_until_ns) +
WallClock (live + REALTIME replay) + TlogDerivedClock (ASAP replay;
advance-on-call; non-monotonic source → ClockOrderingError).
* FrameSource Protocol (next_frame -> NavCameraFrame | None / close)
+ LiveCameraFrameSource (cv2.VideoCapture device index) +
VideoFileFrameSource (cv2.VideoCapture file).
* Build-flag gating: BUILD_VIDEO_FILE_FRAME_SOURCE,
BUILD_LIVE_CAMERA_FRAME_SOURCE (constructor-time check; Tier-0 OFF
refuses construction with FrameSourceConfigError).
* Composition-root factories: build_clock + build_frame_source.
* Injected Clock across every component that previously called
time.monotonic_ns() / time.sleep() directly: c5_state (estimator,
ESKF, fallback watcher, source-label SM, isam2 handle), c8_fc_adapter
(inbound MAVLink + MSP2, AP outbound, iNav outbound, QGC GCS),
c13_fdr writer, c12_operator_tooling httpx flights client. All
constructors default to WallClock() so existing call sites keep
live-binary behaviour without a wiring change.
* AC-4 CI guard (tests/_meta/test_no_direct_time_in_components.py)
AST-scans components/**/*.py for direct time.monotonic_ns /
time.time_ns / time.sleep references and fails loudly with file:line.
* Conformance + factory tests: tests/unit/clock + tests/unit/frame_source.
* Test fixture updates: FallbackWatcher / SourceLabelStateMachine
clock_ns is now required (removed time.monotonic_ns default);
test_az388 patches estimator._clock instead of a module-level time;
test_az393 ardupilot adapter uses a _FixedClock test double.
Excluded per the task spec: TlogReplayFcAdapter (AZ-399), ReplaySink
(AZ-400), compose_replay (AZ-401), CLI (AZ-402), Docker/CI (AZ-403),
E2E fixture (AZ-404), IMU auto-sync (AZ-405).
Co-authored-by: Cursor <cursoragent@cursor.com >
2026-05-12 05:10:01 +03:00
Oleksandr Bezdieniezhnykh
beed43724f
[AZ-381] C5 StateEstimator protocol + factory + C8 DTO reshape
...
- Add StateEstimator Protocol (6 methods, @runtime_checkable) + DTOs
(EstimatorOutput, EstimatorHealth, IsamState, PoseSourceLabel, Quat)
in _types/state.py per state_estimator_protocol.md v1.0.0.
- Add C5 error hierarchy (StateEstimatorError + 3 subclasses) and
C5StateConfig (strategy, keyframe_window, spoof gates,
no_estimate_fallback_s) with __post_init__ validation.
- Add ISam2GraphHandle Protocol + ISam2GraphHandleImpl skeleton (all
4 methods raise NotImplementedError naming AZ-382 as owner).
- Add build_state_estimator factory + bind_state_ingest_thread for
single-writer enforcement; ADR-002 build-flag gating
(BUILD_STATE_<variant>); INFO log on success.
- Strict reshape of legacy EstimatorOutput / EstimatorHealth across
all 6 C8 production files (_outbound_provenance,
_covariance_projector, pymavlink_ardupilot_adapter,
msp2_inav_adapter, mavlink_gcs_adapter, interface) + 6 C8 test
files (UUID frame_id, LatLonAlt position_wgs84, Quat orientation,
PoseSourceLabel enum source_label). Remove ad-hoc DTOs from
_types/pose.py and from C4's public __init__ (EstimatorOutput is a
C5 concept, not a C4 one).
- 20 AZ-381 AC tests (10 ACs + 4 config range + NFR + conformance).
- Full suite: 521 passed, 2 skipped (+20 vs Batch 11).
- Contracts: state_estimator_protocol.md v1.0.0 -> active;
composition_root_protocol.md v1.2.0 -> v1.3.0 (additive state
block + factory + ingest-thread binding).
- Impl report: _docs/03_implementation/batch_12_cycle1_report.md.
Co-authored-by: Cursor <cursoragent@cursor.com >
2026-05-11 05:35:20 +03:00
Oleksandr Bezdieniezhnykh
8a9cf88a46
[AZ-396] [AZ-397] Batch 11: C8 source-set switch + QGC telemetry adapter
...
AZ-396: PymavlinkArdupilotAdapter.request_source_set_switch body sends
MAV_CMD_SET_EKF_SOURCE_SET, awaits COMMAND_ACK with timeout, enforces
Invariant 11 idempotence (1s rate-limit + skip-after-success). Adds
runtime_root.SpoofRecoverySink to bridge C5 spoof-promotion-recovered
signal to the C8 outbound thread via a bounded dispatch queue.
FcConfig gains spoof_recovery_source_set + source_set_switch_timeout_ms.
AZ-397: QgcTelemetryAdapter implements GcsAdapter strategy: MAVLink 2.0
to QGC, emit_summary downsamples 5Hz to configurable summary_rate_hz
[0.5, 5.0] via integer modulo, emit_status_text mirrors to GCS link,
subscribe_operator_commands translates COMMAND_LONG / PARAM_REQUEST_*
/ REQUEST_DATA_STREAM / MISSION_* / SET_MODE into OperatorCommand DTOs
and audits each receipt to FDR. FcKind.GCS_QGC added for PortConfig.
Tests: 25 new (12 AZ-396 + 13 AZ-397); full suite 501 passing, 2 skipped.
Contracts unchanged (additive FcConfig fields, range relaxation on
GcsConfig.summary_rate_hz, additive FcKind enum value).
Co-authored-by: Cursor <cursoragent@cursor.com >
2026-05-11 05:06:56 +03:00
Oleksandr Bezdieniezhnykh
1e0be08e8a
[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 >
2026-05-11 04:47:44 +03:00