mirror of
https://github.com/azaion/gps-denied-onboard.git
synced 2026-06-22 13:31:14 +00:00
[AZ-223] [AZ-224] [AZ-225] [AZ-227] Add runtime gateways
Implement the first runtime component boundaries around the shared contracts so downstream batches can consume typed frame, MAVLink, tile, and FDR behavior with focused tests and batch evidence. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -2,6 +2,18 @@
|
||||
|
||||
from typing import Any, Protocol
|
||||
|
||||
from shared.contracts import FdrEvent
|
||||
from shared.errors import ErrorEnvelope
|
||||
|
||||
from .types import (
|
||||
FdrAppendResult,
|
||||
FdrExportRequest,
|
||||
FdrExportResult,
|
||||
FdrHealth,
|
||||
FdrPayload,
|
||||
FdrSegmentSummary,
|
||||
)
|
||||
|
||||
|
||||
class FlightRecorder(Protocol):
|
||||
"""Append-only event recorder for runtime evidence."""
|
||||
@@ -11,3 +23,99 @@ class FlightRecorder(Protocol):
|
||||
|
||||
def export(self) -> Any:
|
||||
"""Export recorded evidence for post-flight analysis."""
|
||||
|
||||
|
||||
class InMemoryFlightRecorder:
|
||||
"""Bounded append-only recorder for runtime evidence metadata."""
|
||||
|
||||
def __init__(self, segment_limit_bytes: int, storage_limit_bytes: int) -> None:
|
||||
if segment_limit_bytes <= 0:
|
||||
raise ValueError("segment_limit_bytes must be positive")
|
||||
if storage_limit_bytes < segment_limit_bytes:
|
||||
raise ValueError("storage_limit_bytes must be at least one segment")
|
||||
self._segment_limit_bytes = segment_limit_bytes
|
||||
self._storage_limit_bytes = storage_limit_bytes
|
||||
self._segments: list[list[FdrEvent]] = [[]]
|
||||
self._segment_bytes: list[int] = [0]
|
||||
self._used_bytes = 0
|
||||
|
||||
@property
|
||||
def health(self) -> FdrHealth:
|
||||
if self._used_bytes >= self._storage_limit_bytes:
|
||||
return FdrHealth(
|
||||
status="critical",
|
||||
used_bytes=self._used_bytes,
|
||||
max_bytes=self._storage_limit_bytes,
|
||||
message="fdr storage limit reached",
|
||||
)
|
||||
if self._used_bytes >= int(self._storage_limit_bytes * 0.9):
|
||||
return FdrHealth(
|
||||
status="degraded",
|
||||
used_bytes=self._used_bytes,
|
||||
max_bytes=self._storage_limit_bytes,
|
||||
message="fdr storage nearing limit",
|
||||
)
|
||||
return FdrHealth(
|
||||
status="ready",
|
||||
used_bytes=self._used_bytes,
|
||||
max_bytes=self._storage_limit_bytes,
|
||||
message="fdr storage ready",
|
||||
)
|
||||
|
||||
def append_event(self, event: FdrEvent, payload: FdrPayload) -> FdrAppendResult:
|
||||
if self._used_bytes + payload.size_bytes > self._storage_limit_bytes:
|
||||
return FdrAppendResult(
|
||||
appended=False,
|
||||
error=ErrorEnvelope(
|
||||
component="fdr_observability",
|
||||
category="resource",
|
||||
message="fdr storage limit reached",
|
||||
severity="critical",
|
||||
retryable=False,
|
||||
),
|
||||
)
|
||||
|
||||
rollover = False
|
||||
if self._segment_bytes[-1] + payload.size_bytes > self._segment_limit_bytes:
|
||||
self._segments.append([])
|
||||
self._segment_bytes.append(0)
|
||||
rollover = True
|
||||
|
||||
segment_index = len(self._segments) - 1
|
||||
stored_event = event.model_copy(update={"payload_ref": payload.ref})
|
||||
self._segments[segment_index].append(stored_event)
|
||||
self._segment_bytes[segment_index] += payload.size_bytes
|
||||
self._used_bytes += payload.size_bytes
|
||||
|
||||
return FdrAppendResult(
|
||||
appended=True,
|
||||
event=stored_event,
|
||||
segment_id=self._segment_id(segment_index),
|
||||
rollover=rollover,
|
||||
)
|
||||
|
||||
def export(self, request: FdrExportRequest) -> FdrExportResult:
|
||||
segments = tuple(
|
||||
FdrSegmentSummary(
|
||||
segment_id=self._segment_id(index),
|
||||
event_count=len(events),
|
||||
bytes_used=self._segment_bytes[index],
|
||||
)
|
||||
for index, events in enumerate(self._segments)
|
||||
if events
|
||||
)
|
||||
evidence_ref = f"fdr://exports/{request.mission_id}/{request.run_id}/evidence.json"
|
||||
analytics_ref = (
|
||||
f"fdr://exports/{request.mission_id}/{request.run_id}/analytics.parquet"
|
||||
if request.include_analytics
|
||||
else None
|
||||
)
|
||||
return FdrExportResult(
|
||||
produced=True,
|
||||
evidence_ref=evidence_ref,
|
||||
segments=segments,
|
||||
analytics_ref=analytics_ref,
|
||||
)
|
||||
|
||||
def _segment_id(self, index: int) -> str:
|
||||
return f"segment-{index + 1:04d}"
|
||||
|
||||
Reference in New Issue
Block a user