diff --git a/openpilot/starpilot/flm/steerlearnerd.py b/openpilot/starpilot/flm/steerlearnerd.py index 8a4c59c9d6..580b9da68c 100644 --- a/openpilot/starpilot/flm/steerlearnerd.py +++ b/openpilot/starpilot/flm/steerlearnerd.py @@ -584,9 +584,11 @@ def run( # Only an already-existing source-only owner may renew here. Actual # observe/witness/native/source gates stay intact; no numerical work or - # stale pose is promoted. The full post-drain path remains below. + # stale pose is promoted. A retained raw header may still need its first + # qualified response; only the actor owns physical-pose deduplication. + # The full post-drain path remains below. if (live_offer is not None and live_actor is not None and live_history.offer is None and - source_valid and tracking_pose_header > previous_tracking_pose_header): + source_valid and tracking_pose_header > 0): early = _early_tracking_offer(sm, CP, collector, live_actor, source, now_ns=now, clock=clock) if early is not None: live_offer = early diff --git a/openpilot/starpilot/flm/tests/test_early_trial_renewal.py b/openpilot/starpilot/flm/tests/test_early_trial_renewal.py index 838b143793..72f794c13a 100644 --- a/openpilot/starpilot/flm/tests/test_early_trial_renewal.py +++ b/openpilot/starpilot/flm/tests/test_early_trial_renewal.py @@ -124,3 +124,48 @@ def test_final_serialization_still_rejects_native_loss_or_expiry(monkeypatch): for stamp, native in ((now[0], False), (renewed.expires_ns+1, True)): message = steerlearnerd.tracking_message(source(stamp), now_ns=stamp, current_native=native, offer=renewed) assert message.starpilotSteeringTracking.trial.version == 0 + + +def test_actual_loop_revisits_retained_raw_header_before_another_fifo_batch(monkeypatch): + from openpilot.starpilot.flm.tests.test_live_tracking_daemon import NoLegacyFitter + + class RenewalVisited(Exception): + pass + + now = [BASE-10_000_000] + class SM: + updated = {'deviceMotion': False} + logMonoTime = {'deviceMotion': BASE} + def update(self, timeout=None): + if timeout == 0: + self.updated['deviceMotion'] = False + else: + now[0] += 10_000_000 + self.updated['deviceMotion'] = True + # Same received header: prior handling does not prove physical qualification. + self.logMonoTime['deviceMotion'] = BASE + + class PendingActor: + reason = 'unit_pending_offer' + ingress = None + def __init__(self): + self.calls = 0 + self.closed = False + def tick(self, collector, current_source, **kwargs): + assert self.calls == 0, 'retained pose bypassed the early path and drained again' + self.calls += 1 + return offer(BASE) + def close(self, reason): + self.closed = True + return True + + actor, fitter = PendingActor(), NoLegacyFitter() + def renewal(sm, cp, collector, current_actor, current_source, **kwargs): + assert now[0] == BASE+10_000_000 and current_actor is actor + raise RenewalVisited + monkeypatch.setattr(steerlearnerd, '_early_tracking_offer', renewal) + monkeypatch.setattr(steerlearnerd, 'drain_pair', lambda *args: None) + with pytest.raises(RenewalVisited): + steerlearnerd.run(S(), SM(), object(), object(), S(send=lambda *args:None), None, None, + source, None, None, None, None, fitter, None, clock=lambda:now[0], live_actor=actor) + assert actor.closed and fitter.closed diff --git a/prebuilt.json b/prebuilt.json index bca6681602..c954df02b5 100644 --- a/prebuilt.json +++ b/prebuilt.json @@ -1 +1 @@ -{"protected_inventory_sha256": "69bc987a943156453c1211b28177b5d828832341441d643d5d7385cbda9a8e12", "schema_version": 1} +{"protected_inventory_sha256": "d03ff4a75d7b1d2b01459d04f14408ee30fa1a45f92812c954551a6a606c9d8d", "schema_version": 1}