mirror of
https://github.com/azaion/gps-denied-onboard.git
synced 2026-06-22 12:51:12 +00:00
[AZ-228] [AZ-229] Add VIO and satellite sync boundaries
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,96 @@
|
||||
from datetime import datetime, timezone
|
||||
|
||||
from satellite_service import MissionCachePackage, SatelliteSyncBoundary
|
||||
from tile_manager import (
|
||||
GeneratedTileSidecar,
|
||||
GeneratedTileSyncPackage,
|
||||
TileManifestEntry,
|
||||
)
|
||||
|
||||
|
||||
def _manifest_entry() -> TileManifestEntry:
|
||||
return TileManifestEntry(
|
||||
tile_id="tile-1",
|
||||
chunk_id="chunk-1",
|
||||
crs="EPSG:3857",
|
||||
meters_per_pixel=0.3,
|
||||
capture_date="2026-05-01",
|
||||
expires_at=datetime(2026, 6, 1, tzinfo=timezone.utc),
|
||||
content_hash="sha256:tile",
|
||||
expected_content_hash="sha256:tile",
|
||||
sidecar_hash="sha256:sidecar",
|
||||
expected_sidecar_hash="sha256:sidecar",
|
||||
signature_hash="sig:trusted",
|
||||
provenance="suite-satellite-service",
|
||||
footprint={"min_lat": 49.0, "max_lat": 49.1},
|
||||
descriptor_ref="descriptors/chunk-1.vlad",
|
||||
)
|
||||
|
||||
|
||||
def _generated_package() -> GeneratedTileSyncPackage:
|
||||
sidecar = GeneratedTileSidecar(
|
||||
tile_id="generated-1",
|
||||
parent_frame_id="frame-1",
|
||||
parent_covariance_m=2.0,
|
||||
quality_score=0.8,
|
||||
trust_level="generated",
|
||||
provenance="nav-camera-generated",
|
||||
)
|
||||
return GeneratedTileSyncPackage(
|
||||
package_ref="generated/mission-1/sync-package.json",
|
||||
mission_id="mission-1",
|
||||
manifest_delta=({"tile_id": "generated-1", "trust_level": "generated"},),
|
||||
sidecars=(sidecar,),
|
||||
)
|
||||
|
||||
|
||||
def test_pre_flight_import_returns_package_for_tile_manager_validation() -> None:
|
||||
# Arrange
|
||||
boundary = SatelliteSyncBoundary()
|
||||
package = MissionCachePackage(
|
||||
package_id="pkg-1",
|
||||
mission_id="mission-1",
|
||||
manifest_entries=(_manifest_entry(),),
|
||||
)
|
||||
|
||||
# Act
|
||||
result = boundary.import_mission_cache(package, phase="pre_flight")
|
||||
|
||||
# Assert
|
||||
assert result.ready_for_tile_validation is True
|
||||
assert result.manifest_entries[0].tile_id == "tile-1"
|
||||
assert boundary.status().imported_package_ids == ("pkg-1",)
|
||||
|
||||
|
||||
def test_post_flight_upload_records_retryable_failure_for_audit() -> None:
|
||||
# Arrange
|
||||
boundary = SatelliteSyncBoundary(uploader=lambda package: "retryable_failure")
|
||||
|
||||
# Act
|
||||
result = boundary.upload_generated_tiles(_generated_package(), phase="post_flight")
|
||||
|
||||
# Assert
|
||||
assert result.upload_record is not None
|
||||
assert result.upload_record.status == "retryable_failure"
|
||||
assert result.upload_record.retained_for_retry is True
|
||||
assert boundary.status().retry_package_refs == ("generated/mission-1/sync-package.json",)
|
||||
|
||||
|
||||
def test_in_flight_sync_is_blocked_without_calling_network_boundary() -> None:
|
||||
# Arrange
|
||||
calls: list[str] = []
|
||||
|
||||
def uploader(package: GeneratedTileSyncPackage) -> str:
|
||||
calls.append(package.package_ref)
|
||||
return "success"
|
||||
|
||||
boundary = SatelliteSyncBoundary(uploader=uploader)
|
||||
|
||||
# Act
|
||||
result = boundary.upload_generated_tiles(_generated_package(), phase="in_flight")
|
||||
|
||||
# Assert
|
||||
assert result.upload_record is None
|
||||
assert result.error is not None
|
||||
assert result.error.cause == "mid_flight_network_blocked"
|
||||
assert calls == []
|
||||
@@ -0,0 +1,73 @@
|
||||
from shared.contracts import FramePacket, TelemetrySample
|
||||
from vio_adapter import LocalVioAdapter, VioInputPacket
|
||||
|
||||
|
||||
def _frame(**overrides: object) -> FramePacket:
|
||||
payload: dict[str, object] = {
|
||||
"frame_id": "frame-1",
|
||||
"timestamp_ns": 1_000_000,
|
||||
"image_ref": "replay/frame-1.jpg",
|
||||
"calibration_id": "calib-1",
|
||||
"occlusion": "clear",
|
||||
"quality": 0.85,
|
||||
}
|
||||
payload.update(overrides)
|
||||
return FramePacket.model_validate(payload)
|
||||
|
||||
|
||||
def _telemetry(timestamp_ns: int = 1_000_000) -> TelemetrySample:
|
||||
return TelemetrySample(
|
||||
timestamp_ns=timestamp_ns,
|
||||
imu={"accel_x": 0.1, "accel_y": 0.0, "accel_z": 9.8},
|
||||
attitude={"roll": 0.0, "pitch": 0.01, "yaw": 0.02},
|
||||
altitude_m=120.0,
|
||||
airspeed_mps=24.0,
|
||||
gps_health="lost",
|
||||
)
|
||||
|
||||
|
||||
def test_valid_synchronized_packet_emits_vio_state() -> None:
|
||||
# Arrange
|
||||
adapter = LocalVioAdapter()
|
||||
packet = VioInputPacket(frame=_frame(), telemetry_samples=(_telemetry(),))
|
||||
|
||||
# Act
|
||||
result = adapter.process(packet)
|
||||
|
||||
# Assert
|
||||
assert result.error is None
|
||||
assert result.state_packet is not None
|
||||
assert result.state_packet.timestamp_ns == 1_000_000
|
||||
assert result.state_packet.tracking_quality == 0.85
|
||||
assert result.health.state == "ready"
|
||||
|
||||
|
||||
def test_timestamp_mismatch_is_explicit_validation_error() -> None:
|
||||
# Arrange
|
||||
adapter = LocalVioAdapter(timestamp_tolerance_ns=1_000)
|
||||
packet = VioInputPacket(frame=_frame(), telemetry_samples=(_telemetry(2_000_000),))
|
||||
|
||||
# Act
|
||||
result = adapter.process(packet)
|
||||
|
||||
# Assert
|
||||
assert result.state_packet is None
|
||||
assert result.error is not None
|
||||
assert result.error.component == "vio_adapter"
|
||||
assert result.error.cause == "gap_exceeded"
|
||||
assert result.health.state == "degraded"
|
||||
|
||||
|
||||
def test_tracking_loss_degrades_health_without_emitting_absolute_position() -> None:
|
||||
# Arrange
|
||||
adapter = LocalVioAdapter(degraded_quality_threshold=0.35)
|
||||
packet = VioInputPacket(frame=_frame(quality=0.2), telemetry_samples=(_telemetry(),))
|
||||
|
||||
# Act
|
||||
result = adapter.process(packet)
|
||||
|
||||
# Assert
|
||||
assert result.state_packet is not None
|
||||
assert result.health.state == "degraded"
|
||||
assert "latitude_deg" not in result.state_packet.model_dump()
|
||||
assert "longitude_deg" not in result.state_packet.model_dump()
|
||||
Reference in New Issue
Block a user