[AZ-490] C5 set_takeoff_origin entrypoint + bounded-delta GPS gate

Add operator warm-start path to C5 StateEstimator Protocol and both
implementations (GtsamIsam2StateEstimator, EskfStateEstimator), plus
the third clause of the AZ-385 spoof-promotion gate.

- StateEstimator Protocol: set_takeoff_origin(origin, sigma_horiz_m,
  sigma_vert_m) -> None.
- iSAM2: PriorFactorPose3 at origin with diagonal sigmas, single
  isam2.update().
- ESKF: zero _nominal_pos, overwrite _P position block with sigma**2.
- SourceLabelStateMachine.process_gps_sample bounded-delta clause:
  WgsConverter.horizontal_distance_m vs smoother estimate; reject
  resets the dwell-time counter so AZ-385 cannot re-promote off bad
  GPS.
- New EstimatorAlreadyStartedError (StateEstimatorConfigError
  subclass) on late call after first add_*.
- C5StateConfig: spoof_promotion_bounded_delta_m=200,
  default_takeoff_origin_sigma_horiz_m=5,
  default_takeoff_origin_sigma_vert_m=10.
- New GpsSample DTO + WgsConverter.horizontal_distance_m helper.
- 4 new FDR kinds (cold_start_origin.{set,unavailable},
  gps_bounded_delta.{accept,reject}) registered in AZ-272 schema.
- 33 new unit tests cover AC-1..AC-15; full repo 750 passed / 2
  skipped (pre-existing CI tooling skips).

Docs synced: protocol contract, C5 component description,
architecture, glossary, system-flows, C10 provisioning description.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Oleksandr Bezdieniezhnykh
2026-05-12 02:53:58 +03:00
parent 72a06edab0
commit 8a83166261
23 changed files with 1640 additions and 26 deletions
@@ -69,6 +69,31 @@ KNOWN_PAYLOAD_KEYS: Final[dict[str, frozenset[str]]] = {
"clean_shutdown",
}
),
# AZ-490 / E-C5: operator-origin cold-start ladder + bounded-delta GPS gate.
"c5.cold_start_origin.set": frozenset(
{"source", "lat_deg", "lon_deg", "alt_m", "sigma_horiz_m", "sigma_vert_m"}
),
"c5.cold_start_origin.unavailable": frozenset({"reason"}),
"c5.gps_bounded_delta.accept": frozenset(
{
"sample_lat",
"sample_lon",
"smoother_lat",
"smoother_lon",
"distance_m",
"threshold_m",
}
),
"c5.gps_bounded_delta.reject": frozenset(
{
"sample_lat",
"sample_lon",
"smoother_lat",
"smoother_lon",
"distance_m",
"threshold_m",
}
),
}
KNOWN_KINDS: Final[frozenset[str]] = frozenset(KNOWN_PAYLOAD_KEYS.keys())