mirror of
https://github.com/azaion/gps-denied-onboard.git
synced 2026-06-22 17:21:13 +00:00
Decompose Step 6 snapshot: 140 task specs + contract docs
Closes out greenfield Step 6 (Decompose) for all 14 components (C1-C13 + cross-cutting helpers/replay). Covers tasks AZ-266..AZ-446 plus the _dependencies_table.md and component contract documents. State file updated to greenfield Step 7 (Implement), not_started. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,103 @@
|
||||
# C8 FcAdapter / GcsAdapter Protocols + DTOs + Factories + Composition
|
||||
|
||||
**Task**: AZ-390_c8_adapter_protocol
|
||||
**Name**: C8 `FcAdapter` + `GcsAdapter` Protocols + DTOs + errors + composition factories
|
||||
**Description**: Define the public `FcAdapter` and `GcsAdapter` Protocols (PEP 544 `@runtime_checkable`), the C8 DTOs (`PortConfig`, `FcKind` enum, `FcTelemetryFrame`, `TelemetryKind` enum + payload union, `FlightStateSignal`, `FlightState` enum, `GpsHealth`, `GpsStatus` enum, `Severity` enum, `EmittedExternalPosition`, `OperatorCommand`), the error hierarchy (`FcAdapterError` family + `GcsAdapterError` family per the contract), and the composition-root factories `build_fc_adapter(...) -> FcAdapter` + `build_gcs_adapter(...) -> GcsAdapter` with strategy resolution (`config.fc.adapter`, `config.gcs.adapter`) and `BUILD_FC_<variant>` / `BUILD_GCS_<variant>` flag gating per ADR-002. Composition root binds C8 outbound (`emit_external_position`, `emit_status_text`, `request_source_set_switch`) to a single emit thread; C8 inbound (`subscribe_telemetry`) fires on the inbound decode thread. Shared helpers (`WgsConverter` AZ-279, `SE3Utils` AZ-277, `FdrClient` AZ-273, `Clock`) constructor-injected. Config schema extension for `fc.{adapter, port_device, port_baud, signing_key_source}` and `gcs.{adapter, port_device, port_baud, summary_rate_hz}`. No wire encoding, no signing logic, no telemetry decoding in scope here — pure scaffolding the seven downstream consumer tasks depend on.
|
||||
**Complexity**: 3 points
|
||||
**Dependencies**: AZ-263, AZ-269, AZ-270, AZ-273 (`FdrClient`), AZ-277 (`SE3Utils`), AZ-279 (`WgsConverter`), AZ-266
|
||||
**Component**: c8_fc_adapter (epic AZ-261 / E-C8)
|
||||
**Tracker**: AZ-390
|
||||
**Epic**: AZ-261 (E-C8)
|
||||
|
||||
### Document Dependencies
|
||||
|
||||
- `_docs/02_document/contracts/c8_fc_adapter/fc_adapter_protocol.md` — the public contract this task implements.
|
||||
- `_docs/02_document/components/10_c8_fc_adapter/description.md` — § 1 overview, § 2 interfaces, § 5 implementation details.
|
||||
- `_docs/02_document/architecture.md` — ADR-001, ADR-002, ADR-009.
|
||||
- `_docs/02_document/module-layout.md` — `c8_fc_adapter` Per-Component Mapping.
|
||||
|
||||
## Problem
|
||||
|
||||
Without this task, no concrete C8 adapter has a Protocol to register against; the runtime root cannot wire C8 to C1 / C5 (which receive `ImuWindow` / `AttitudeWindow` / `GpsHealth` / `FlightStateSignal` exclusively via the constructor-injected `FcAdapter` interface); the seven downstream consumer tasks (inbound subscription, covariance projector, AP outbound, iNav outbound, signing handshake, source-set switch, GCS adapter) have no shared DTO surface to encode/decode against.
|
||||
|
||||
## Outcome
|
||||
|
||||
- `src/gps_denied_onboard/components/c8_fc_adapter/interface.py` — `FcAdapter`, `GcsAdapter` Protocols with all methods per the contract.
|
||||
- `src/gps_denied_onboard/components/c8_fc_adapter/__init__.py` — re-exports `FcAdapter`, `GcsAdapter`, `EmittedExternalPosition`.
|
||||
- `src/gps_denied_onboard/_types/fc.py` — `PortConfig`, `FcKind`, `FcTelemetryFrame`, `TelemetryKind`, `FlightStateSignal`, `FlightState`, `GpsHealth`, `GpsStatus`, `Severity`, `EmittedExternalPosition`, `OperatorCommand` (all frozen + slots).
|
||||
- `src/gps_denied_onboard/components/c8_fc_adapter/errors.py` — full error hierarchy.
|
||||
- `src/gps_denied_onboard/runtime_root/fc_factory.py` — `build_fc_adapter(...)` + `build_gcs_adapter(...)`. Lazy-import per ADR-002.
|
||||
- Composition-root extension: invoke `build_fc_adapter` AFTER C5; invoke `build_gcs_adapter` AFTER `build_fc_adapter`; bind outbound to ONE emit thread (single-writer invariant).
|
||||
- Config schema extension for `fc.*` + `gcs.*` fields.
|
||||
- INFO log on successful build: `kind="c8.adapter.strategy_loaded"` with `{fc_kind, gcs_kind}`.
|
||||
|
||||
## Scope
|
||||
|
||||
### Included
|
||||
- Both Protocols with all methods.
|
||||
- All DTOs + enums.
|
||||
- Error hierarchy.
|
||||
- Both factories + composition-root wiring.
|
||||
- Single-writer thread enforcement for outbound.
|
||||
- Config schema extension.
|
||||
- Unit tests: Protocol conformance, DTO immutability + slots, factory rejection on unknown strategy + missing build flag, single-thread enforcement.
|
||||
|
||||
### Excluded
|
||||
- Inbound MAVLink + MSP2 decoder bodies — owned by next task.
|
||||
- `CovarianceProjector` — owned by next task.
|
||||
- `PymavlinkArdupilotAdapter` outbound body — owned by AP outbound task.
|
||||
- `Msp2InavAdapter` outbound body — owned by iNav outbound task.
|
||||
- MAVLink 2.0 signing handshake — owned by signing task.
|
||||
- D-C8-2 source-set switch body — owned by source-set task.
|
||||
- `QgcTelemetryAdapter` body — owned by GCS task.
|
||||
- C8-IT/PT/ST tests — deferred to E-BBT (AZ-262).
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
**AC-1: Protocol conformance** — `runtime_checkable` `isinstance` returns True for fakes implementing each Protocol's full method set.
|
||||
|
||||
**AC-2: DTOs frozen + slots** — `FrozenInstanceError` on mutation; `__slots__` non-empty for every DTO.
|
||||
|
||||
**AC-3: Enum membership** — `FcKind` has 2 values (ARDUPILOT_PLANE, INAV); `FlightState` has 5 (INIT/ARMED/IN_FLIGHT/ON_GROUND/FAILED); `GpsStatus` has 5 (NO_FIX/DEGRADED/STABLE/STABLE_NON_SPOOFED/SPOOFED); `Severity` has 3 (INFO=6, WARNING=4, ERROR=3 — values mirror MAVLink STATUSTEXT severities).
|
||||
|
||||
**AC-4: Factory rejects missing build flag** — `config.fc.adapter = "ardupilot_plane"` with `BUILD_FC_ARDUPILOT_PLANE=OFF` → `FcAdapterConfigError("BUILD_FC_ARDUPILOT_PLANE is OFF...")`.
|
||||
|
||||
**AC-5: Factory rejects unknown strategy at config-load** — `config.fc.adapter = "garbage"` → `FcAdapterConfigError` at config load (NOT at build time).
|
||||
|
||||
**AC-6: Single-writer thread for outbound** — composition root binds outbound to ONE thread; second binding raises `RuntimeError`.
|
||||
|
||||
**AC-7: GCS factory parallel coverage** — same set of acceptance behaviours for `build_gcs_adapter` against the `GcsAdapter` Protocol.
|
||||
|
||||
**AC-8: Public API re-exports** — `from gps_denied_onboard.components.c8_fc_adapter import FcAdapter, GcsAdapter, EmittedExternalPosition` resolves; internal modules NOT in `__all__`.
|
||||
|
||||
**AC-9: Error hierarchy catchability** — every FC error caught by `except FcAdapterError`; every GCS error caught by `except GcsAdapterError`. `SourceSetSwitchNotSupportedError` is also a `SourceSetSwitchError` (sub-typed for iNav rejection).
|
||||
|
||||
**AC-10: INFO log on build** — successful build logs `kind="c8.adapter.strategy_loaded"` once per adapter with the strategy name + port device.
|
||||
|
||||
## Non-Functional Requirements
|
||||
|
||||
- `build_fc_adapter` p99 ≤ 50 ms.
|
||||
- `build_gcs_adapter` p99 ≤ 50 ms.
|
||||
|
||||
## Constraints
|
||||
|
||||
- `@runtime_checkable` on both Protocols; DTOs `frozen=True, slots=True`.
|
||||
- Lazy-import per ADR-002.
|
||||
- Single-thread binding enforced for outbound (AC-6).
|
||||
- Public API surface limited to the two re-export sets (per `module-layout.md`).
|
||||
|
||||
## Risks & Mitigation
|
||||
|
||||
- **Risk**: Protocol surface changes after consumer tasks land. *Mitigation*: this task ships first; downstream tasks reference the Protocol shape locked here. Any extension is additive (new method on the Protocol implies a default no-op fallback or a follow-up Protocol version bump documented in the contract).
|
||||
- **Risk**: Single-thread binding bug breaks the multi-consumer (C1 + C5) inbound path. *Mitigation*: AC-6 covers ONLY outbound; inbound subscribe-callback semantics are documented as fire-on-decode-thread + consumer responsibility (Invariant 8).
|
||||
|
||||
## Runtime Completeness
|
||||
|
||||
- **Named capability**: C8 Protocols + DTOs + factories.
|
||||
- **Production code**: real Protocols, real DTOs, real error hierarchy, real factories, real composition-root wiring.
|
||||
- **Allowed external stubs**: test fakes only; no production code may import `FcAdapterStub` outside tests.
|
||||
- **Unacceptable substitutes**: hardcoding the C8 strategy class in the runtime root (defeats ADR-009); skipping the Protocol surface.
|
||||
|
||||
## Contract
|
||||
|
||||
Implements `_docs/02_document/contracts/c8_fc_adapter/fc_adapter_protocol.md`.
|
||||
Reference in New Issue
Block a user