"""Actual Controls -> GlobalRuntime -> LaC at100Hz with real cereal deliveries. Response certificates below are explicit unit fixtures, not physical model qualification. Admission, native permission, source leases, controller hooks, application receipts and steering encoding execute their production paths. """ from contextlib import contextmanager from dataclasses import replace import math from unittest import mock import pytest from opendbc.car.car_helpers import interfaces from opendbc.car.toyota.values import CAR from openpilot.cereal import messaging from openpilot.cereal.services import SERVICE_LIST from openpilot.starpilot.flm.canonical_surface_adapter import full_profile_sha256 from openpilot.starpilot.flm.live_trial_contract import LiveTrialBinding from openpilot.starpilot.flm.live_trial_owner import LiveTrialApplication from openpilot.starpilot.flm.live_trial_wire import write_trial, write_tracking_source from openpilot.starpilot.flm.runtime_adapter_registry import read_source from openpilot.starpilot.flm.tests.test_live_trial_owner import offer, response from openpilot.starpilot.flm.tests.test_registered_controls_global import controls_for, feed, source_packet LIVE = 'starpilotSteeringTracking' LEGACY = 'autoSteeringLearning' BASE = 800_000_000_000 DT = 10_000_000 GENERATION = 'a' * 32 FED = frozenset(('carState', 'selfdriveState', 'deviceState', 'vehicleParameters', 'longitudinalPlan', 'lateralDelay', 'onroadEvents', 'modelV2', 'driverMonitoringState', 'extrinsicsCalibration', LEGACY, LIVE, 'pandaStates', 'aolAxisState', 'aolSafetyWire')) class LiveControls: def __init__(self, controls): self.controls = controls self.tick = -1 self.publications = 0 self.latest = None self.last_command = None self.extra_last = {} @property def runtime(self): return self.controls.global_learning def source(self, now): return read_source(self.controls.CP, self.controls.params, now_ns=now, calibration=(0.0, 0.0, 0.0), calibration_generation=GENERATION, vehicle_session='live-consumer-integration-drive') def initial_offer(self): now = BASE + (self.tick + 1) * DT runtime = self.runtime source = self.source(now) assert runtime.identity and runtime.live_vehicle_model_generation assert runtime.identity_source_epoch == source.source_epoch binding = LiveTrialBinding(runtime.identity, runtime.descriptor.cp_sha256, runtime.descriptor.controller_revision, runtime.baseline_revision, source.source_epoch, source.reset, source.calibration_generation, runtime.live_vehicle_model_generation, runtime.native_identity, '0' * 64, source.persistent_source_epoch, runtime.native_required) binding, witness = response(binding, now) original = offer(now) return replace(original, binding=binding, response_witness=witness, baseline_sha256=full_profile_sha256(binding.baseline_revision, original.baseline), target_sha256=full_profile_sha256(binding.baseline_revision, original.target), deadline_ns=now + 120_000_000_000) def renew(self, proposal, now): from openpilot.starpilot.flm.live_tracking_response import ResponseWitness, prepare_response_witness, validate_response_witness raw = validate_response_witness(proposal.response_witness, proposal.binding, proposal.observed_ns) witness = prepare_response_witness(ResponseWitness(raw.certificate, proposal.binding, now, now, now, proposal.started_ns - 500_000_000, 8, 0.001, now + 100_000_000), proposal.binding, now) return replace(proposal, sequence=self.publications + 1, observed_ns=now, expires_ns=now + 100_000_000, response_witness=witness) def frame(self, proposal=None, *, publish=True, source_valid=True, native_allowed=True, driver=False, active=True, model_age=0, lease_ns=100_000_000): self.tick += 1 now = BASE + self.tick * DT controls = self.controls original_message = messaging.new_message def packet(service, *args, **kwargs): event = original_message(service, *args, **kwargs) if service == 'carState': event.carState.steeringAngleDeg = math.degrees(controls.VM.get_steer_from_curvature(-0.0002, 30.0, 0.0)) elif service == 'extrinsicsCalibration': event.extrinsicsCalibration.calStatus = 'calibrated' event.extrinsicsCalibration.validBlocks = 50 event.extrinsicsCalibration.rpyCalibSpread = [0.0, 0.001, 0.001] return event from openpilot.starpilot.aol.wire import encode_safety def native_packet(value): # Change only the typed Panda input on denial. The positive path uses # the existing real wire encoder and Controls' current_native parser. if not native_allowed: value = replace(value, lateralAllowed=False, longitudinalAllowed=False, pandaInventory=tuple(replace(slot, controlsAllowed=False) for slot in value.pandaInventory)) return encode_safety(value) with (mock.patch.object(messaging, 'new_message', side_effect=packet), mock.patch('openpilot.starpilot.aol.wire.encode_safety', side_effect=native_packet)): feed(controls, now, self.tick, speed=30.0, calibration=(0.0, 0.0, 0.0), override=driver, active=active, model_age=model_age) events = [] # The existing feed fixture supplies the actual GlobalRuntime inputs. # Populate other ordinary Controls subscriptions at their declared rates # too, so all_checks() is a real aggregation assertion, not a flag patch. for service in controls.sm.services: frequency = SERVICE_LIST[service].frequency if service in FED or service in controls.sm.ignore_alive or frequency <= 0: continue period = round(1e9 / frequency) if service not in self.extra_last: for stamp in (now - 2 * period, now - period): event = messaging.new_message(service, valid=True, logMonoTime=stamp) controls.sm.update_msgs(stamp / 1e9, [messaging.log_from_bytes(event.to_bytes())]) self.extra_last[service] = now - period if now - self.extra_last[service] >= period: event = messaging.new_message(service, valid=True, logMonoTime=now) events.append(messaging.log_from_bytes(event.to_bytes())) self.extra_last[service] = now if self.tick % 25 == 0: events.append(source_packet(controls, now, self.tick, (0.0, 0.0, 0.0))) panda_period = round(100 / SERVICE_LIST['pandaStates'].frequency) if self.tick % panda_period == 0: event = messaging.new_message('pandaStates', len(controls.CP.safetyConfigs), valid=True, logMonoTime=now) for panda, config in zip(event.pandaStates, controls.CP.safetyConfigs, strict=True): panda.safetyModel, panda.safetyParam = config.safetyModel, config.safetyParam panda.alternativeExperience = controls.CP.alternativeExperience panda.controlsAllowed = native_allowed and str(config.safetyModel) not in ('silent', 'noOutput') events.append(messaging.log_from_bytes(event.to_bytes())) if publish and self.tick % 5 == 0: event = messaging.new_message(LIVE, valid=source_valid, logMonoTime=now) write_tracking_source(event, self.source(now)) if proposal is not None: self.latest = replace(self.renew(proposal, now), expires_ns=now + lease_ns) write_trial(event, self.latest) self.publications += 1 events.append(messaging.log_from_bytes(event.to_bytes())) controls.sm.update_msgs(now / 1e9, events) with mock.patch('openpilot.selfdrive.controls.controlsd.time.monotonic_ns', return_value=now): command, _ = controls.state_control() assert controls.global_learning is not None # Optional exceptions must not silently satisfy denials. assert not controls.sm.simulation assert math.isfinite(command.actuators.torque) and abs(command.actuators.torque) <= 1.0 self.last_command = command return now, command def assert_applied(self): runtime = self.runtime owner = runtime.live_trial_owner assert runtime.reason == 'qualified', (self.tick, runtime.reason) assert owner is not None and self.controls.LaC.automatic_flm_effects is owner assert owner.used and owner.frame_complete(runtime.descriptor.domain) assert type(runtime.outcome) is LiveTrialApplication assert runtime.outcome.status == 'applied' assert runtime.outcome.control_ns == BASE + self.tick * DT assert runtime.outcome.sequence == self.latest.sequence assert runtime.profile is None and runtime.validation is None assert owner.confirmed_full == self.latest.baseline or owner.offer.phase == 'confirmed' return owner.applied_full['ff_gain_left'] def assert_neutral(self): assert self.controls.LaC.automatic_flm_effects is None owner = self.runtime.live_trial_owner if owner is not None: assert owner.source is None and not any(owner.applied_full.values()) assert owner.application is None outcome = self.runtime.outcome if outcome is not None: from openpilot.starpilot.flm.adaptive_profile import ApplicationOutcome # Active original control still emits fresh legacy neutral bookkeeping. # This must never be a stale live-trial acknowledgement or correction. assert type(outcome) is ApplicationOutcome assert outcome.observed_ns == BASE + self.tick * DT assert outcome.identity == self.runtime.identity and outcome.baseline_revision == self.runtime.baseline_revision assert outcome.status in ('collecting', 'paused') and outcome.reason == self.runtime.reason assert outcome.profile_sequence == 0 and outcome.actual_coordinates == (0.0, 0.0) assert outcome.physical_model_revision == '' assert outcome.effect_manifest_sha256 == outcome.effect_original_anchor == outcome.effect_applied_profile_sha256 == '' assert self.runtime.profile is None and self.runtime.validation is None @contextmanager def live_controls(): cp = interfaces[CAR.TOYOTA_RAV4_TSS2].get_non_essential_params(CAR.TOYOTA_RAV4_TSS2) with controls_for(cp, 'standard', True) as controls: fixture = LiveControls(controls) assert LIVE in controls.sm.services and 'pandaStates' in controls.sm.services from openpilot.starpilot.aol.vehicle import policy_for assert controls.aol_replay and controls.ordinary_axis_ack_required assert policy_for(controls.CP).ordinary_axis_ack_required assert {'aolAxisState', 'aolSafetyWire'} <= set(controls.sm.services) for _ in range(60): fixture.frame() assert controls.sm.freq_ok[LIVE] and controls.sm.freq_ok[LEGACY] assert controls.sm.freq_ok['pandaStates'] assert controls.sm.freq_ok['aolAxisState'] and controls.sm.freq_ok['aolSafetyWire'] assert fixture.runtime.native_required and fixture.runtime.native_identity assert controls.flm_native_witness is not None yield fixture def test_production100hz_consumer_across4hz_metadata_and20hz_trials_reaches_actual_can(): from openpilot.starpilot.flm.tests.test_steering_projection import make_interface, install_observer, settle, control, apply with live_controls() as fixture: proposal = fixture.initial_offer() original_tune = fixture.controls.LaC.torque_params.to_dict() native = make_interface(CAR.TOYOTA_RAV4_TSS2) assert install_observer(native) settle(native, control(0.0)) previous = 0.0 actual_packets = 0 for _ in range(120): _, command = fixture.frame(proposal) value = fixture.assert_applied() assert 0 < value <= 0.005 assert 0 <= value - previous <= 0.00005 + 1e-12 previous = value assert fixture.controls.sm.freq_ok[LIVE] and fixture.controls.sm.freq_ok[LEGACY] assert fixture.controls.sm.all_checks() # Ordinary aggregation sees no artificial rate failure. # Card receives the serialized SubMaster reader, not the Controls builder. reader = command.as_reader() assert reader.actuators.torque == command.actuators.torque and reader.latActive == command.latActive _, packets, witness = apply(native, reader) if witness is not None: actual_packets += 1 assert (witness.can_address, witness.can_data, witness.can_bus) in packets assert actual_packets > 0 assert previous == pytest.approx(0.005) assert fixture.controls.LaC.torque_params.to_dict() == original_tune assert fixture.controls.params.get('AutomaticSteeringLiveTrials') is None def test_missed20hz_publications_expire_at100ms_and_old_pending_cannot_resume_or_confirm(): with live_controls() as fixture: proposal = fixture.initial_offer() for _ in range(21): fixture.frame(proposal) fixture.assert_applied() last = fixture.controls.sm.recv_time[LIVE] for _ in range(10): now, _ = fixture.frame(proposal, publish=False) assert now <= round(last * 1e9) + 100_000_000 fixture.assert_applied() fixture.frame(proposal, publish=False) fixture.assert_neutral() assert fixture.controls.sm.alive[LIVE] # Generic20Hz liveness is500ms, not the100ms permission lease. for _ in range(9): fixture.frame(proposal) fixture.assert_neutral() confirmed = replace(proposal, phase='confirmed') for _ in range(5): fixture.frame(confirmed) fixture.assert_neutral() assert fixture.runtime.live_trial_owner.confirmed_full == proposal.baseline assert fixture.controls.params.get('AutomaticSteeringLiveTrials') is None replacement = replace(proposal, trial_id='c' * 64, target_sequence=2) for _ in range(10): fixture.frame(replacement) if fixture.tick % 5 == 0 or fixture.runtime.reason == 'qualified': fixture.assert_applied() @pytest.mark.parametrize('fault', ['driver', 'native', 'source', 'model', 'inactive']) def test_current_fault_neutralizes_then_rollback_requires_fresh_permission(fault): with live_controls() as fixture: proposal = fixture.initial_offer() for _ in range(20): fixture.frame(proposal) fixture.assert_applied() assert fixture.tick == 79 before = fixture.runtime.live_trial_owner.applied_full['ff_gain_left'] assert before > 0 kwargs = {'driver': fault == 'driver', 'native_allowed': fault != 'native', 'source_valid': fault != 'source', 'model_age': 1_000_000_000 if fault == 'model' else 0, 'active': fault != 'inactive'} fixture.frame(proposal, **kwargs) fixture.assert_neutral() rollback = replace(proposal, phase='rollback', target=proposal.baseline, target_sha256=proposal.baseline_sha256) # The rollback offer is configuration; fresh native/current source admission # remains necessary before it can submit a controller command. for _ in range(10): fixture.frame(rollback, **{**kwargs, 'native_allowed': False}) fixture.assert_neutral() assert fixture.runtime.live_trial_owner.confirmed_full == proposal.baseline for _ in range(15): fixture.frame(rollback) if fixture.tick >= 100: fixture.assert_applied() else: fixture.assert_neutral() assert fixture.runtime.live_trial_owner.confirmed_full == proposal.baseline assert not any(fixture.runtime.live_trial_owner.applied_full.values()) assert fixture.runtime.outcome.confirmed_target_sha256 == '' from openpilot.starpilot.flm.learning_wire import write_control, read_control now = BASE + fixture.tick * DT evidence = fixture.runtime.capture_evidence(control_ns=now, state_ns=int(fixture.controls.sm.logMonoTime['carState']), active=bool(fixture.last_command.latActive), limited=False) assert evidence is not None and evidence.live_applied_coordinates == (0.0,) * 30 event = messaging.new_message('starpilotLateralState', valid=True, logMonoTime=now) write_control(event.starpilotLateralState, evidence) assert read_control(messaging.log_from_bytes(event.to_bytes())).live_applied_coordinates == (0.0,) * 30 def test_confirmed_requires_actual_controller_ack_and_retains_exact_older_publication_sequence(): from openpilot.starpilot.flm.live_trial_wire import write_application, read_application with live_controls() as fixture: proposal = fixture.initial_offer() for _ in range(110): fixture.frame(proposal) fixture.assert_applied() assert fixture.runtime.live_trial_owner.confirmed_full == proposal.baseline confirmed = replace(proposal, phase='confirmed') for _ in range(5): now, _ = fixture.frame(confirmed) fixture.assert_applied() application = fixture.runtime.outcome assert application.confirmed_target_sha256 == proposal.target_sha256 assert fixture.runtime.live_trial_owner.confirmed_full == proposal.target event = messaging.new_message('starpilotLateralState', valid=True, logMonoTime=now) write_application(event.starpilotLateralState, application) later = replace(fixture.latest, sequence=fixture.latest.sequence + 1) parsed = read_application(messaging.log_from_bytes(event.to_bytes()).starpilotLateralState, now_ns=now, offer=later) assert parsed is not None and parsed.sequence == application.sequence assert parsed.sequence < later.sequence assert parsed.applied == proposal.target assert parsed.confirmed_target_sha256 == proposal.target_sha256 assert read_application(event.starpilotLateralState, now_ns=now + 100_000_001, offer=later) is None def test_actual_control_evidence_keeps_owner_vm_anchor_and_typed_wire_bounds(): from openpilot.starpilot.flm.learning_wire import write_control, read_control with live_controls() as fixture: proposal = fixture.initial_offer() now, command = fixture.frame(proposal) fixture.assert_applied() evidence = fixture.runtime.capture_evidence(control_ns=now, state_ns=int(fixture.controls.sm.logMonoTime['carState']), active=bool(command.latActive), limited=False) assert evidence is not None assert evidence.vehicle_model_anchor == tuple(fixture.runtime.vm_anchor) assert len(evidence.vehicle_model_anchor) == 3 actual = tuple(value for _, value in fixture.runtime.outcome.applied) assert len(evidence.live_applied_coordinates) == 30 assert evidence.live_applied_coordinates == actual assert evidence.live_applied_coordinates != tuple(value for _, value in proposal.target) assert evidence.effect_trace is None # A live stage acknowledgement is not a replay trace. assert evidence.applied_coordinates == (0.0, 0.0) event = messaging.new_message('starpilotLateralState', valid=True, logMonoTime=now) write_control(event.starpilotLateralState, evidence) decoded = read_control(messaging.log_from_bytes(event.to_bytes())) assert decoded.vehicle_model_anchor == evidence.vehicle_model_anchor assert decoded.live_applied_coordinates == actual assert decoded.effect_trace is None # Historical recordings did not carry an anchor; no reconstruction mints one. write_control(event.starpilotLateralState, replace(evidence, vehicle_model_anchor=(), live_applied_coordinates=())) historical = read_control(messaging.log_from_bytes(event.to_bytes())) assert historical.vehicle_model_anchor == () assert historical.live_applied_coordinates == () for invalid in ((1.0,), (1.0, 2.0), (1.0, 2.0, 3.0, 4.0), (1.0, 0.0, 3.0), (1.0, math.nan, 3.0), (1.0, math.inf, 3.0)): with pytest.raises(ValueError, match='control evidence'): write_control(event.starpilotLateralState, replace(evidence, vehicle_model_anchor=invalid)) write_control(event.starpilotLateralState, evidence) event.starpilotLateralState.learningEvidence.vehicleModelAnchor = invalid with pytest.raises(ValueError, match='control version or bound'): read_control(messaging.log_from_bytes(event.to_bytes())) for invalid in ((0.0,), (0.0,) * 29, (0.0,) * 31, (math.nan,) * 30, (math.inf,) * 30): with pytest.raises(ValueError, match='control evidence'): write_control(event.starpilotLateralState, replace(evidence, live_applied_coordinates=invalid)) write_control(event.starpilotLateralState, evidence) event.starpilotLateralState.learningEvidence.liveAppliedCoordinates = invalid with pytest.raises(ValueError, match='control version or bound'): read_control(messaging.log_from_bytes(event.to_bytes())) now, command = fixture.frame(proposal, driver=True) fixture.assert_neutral() interrupted = fixture.runtime.capture_evidence(control_ns=now, state_ns=int(fixture.controls.sm.logMonoTime['carState']), active=bool(command.latActive), limited=False) assert interrupted is None # No previous live map or acknowledgement survives as current evidence. assert fixture.runtime.outcome is None assert not any(fixture.runtime.live_trial_owner.applied_full.values()) @pytest.mark.parametrize('lease_ns,publication_delay_ns', [(10_000_000, 20_000_000), (100_000_000, 30_000_001)]) def test_consumed_live_map_without_current_publication_proof_is_absent_not_neutral(lease_ns, publication_delay_ns): with live_controls() as fixture: proposal = fixture.initial_offer() now, command = fixture.frame(proposal, lease_ns=lease_ns) fixture.assert_applied() actual = fixture.runtime.outcome.applied assert any(value != 0.0 for _, value in actual) assert fixture.runtime.live_trial_owner.used assert fixture.runtime.live_trial_owner.frame_complete(fixture.runtime.descriptor.domain) # The first case expires its real offer before publication within30ms; # the second still has an offer lease but misses same-frame publication. publication_ns = now + publication_delay_ns if lease_ns < publication_delay_ns: assert publication_ns > fixture.latest.expires_ns else: assert publication_ns <= fixture.latest.expires_ns evidence = fixture.runtime.capture_evidence(control_ns=publication_ns, state_ns=int(fixture.controls.sm.logMonoTime['carState']), active=bool(command.latActive), limited=False) assert evidence is None assert fixture.runtime.outcome.applied == actual # Historical consumption is not relabelled as neutral. def test_interrupted_pending_consumer_reports_rejection_after_actual_current_frame(): from openpilot.starpilot.flm.application_wire import write_application from openpilot.starpilot.flm.live_trial_wire import read_application with live_controls() as fixture: proposal = fixture.initial_offer() for _ in range(21): fixture.frame(proposal) for _ in range(11): fixture.frame(proposal, publish=False) for _ in range(9): now, _ = fixture.frame(proposal) fixture.assert_neutral() owner = fixture.runtime.live_trial_owner assert owner.interrupted and owner.offer.phase == 'trial' evidence = fixture.runtime.capture_evidence(control_ns=now, state_ns=int(fixture.controls.sm.logMonoTime['carState']), active=bool(fixture.last_command.latActive), limited=False) assert evidence is not None and evidence.valid and evidence.native_allowed refusal = fixture.runtime.outcome assert type(refusal) is LiveTrialApplication and refusal.status == 'rejected' assert refusal.reason == 'pending_trial_interrupted' and refusal.confirmed_target_sha256 == '' assert refusal.trial_id == fixture.latest.trial_id and refusal.target_sequence == fixture.latest.target_sequence assert not any(value for _, value in refusal.applied) and owner.confirmed_full == proposal.baseline message = messaging.new_message('starpilotLateralState', valid=True, logMonoTime=now) write_application(message.starpilotLateralState, refusal) decoded = read_application(messaging.log_from_bytes(message.to_bytes()).starpilotLateralState, now_ns=now, offer=fixture.latest) assert decoded == refusal assert fixture.controls.params.get('AutomaticSteeringLiveTrials') is None @pytest.mark.parametrize('fault', ['driver', 'native', 'source', 'model', 'inactive']) def test_pending_rejection_never_bypasses_current_consumer_faults(fault): with live_controls() as fixture: proposal = fixture.initial_offer() for _ in range(20): fixture.frame(proposal) for _ in range(11): fixture.frame(proposal, publish=False) for _ in range(9): fixture.frame(proposal) assert fixture.tick == 99 # Next frame delivers real10Hz Panda and20Hz model/source denials. now, _ = fixture.frame(proposal, driver=fault == 'driver', native_allowed=fault != 'native', source_valid=fault != 'source', model_age=1_000_000_000 if fault == 'model' else 0, active=fault != 'inactive') fixture.runtime.capture_evidence(control_ns=now, state_ns=int(fixture.controls.sm.logMonoTime['carState']), active=bool(fixture.last_command.latActive), limited=False) outcome = fixture.runtime.outcome assert type(outcome) is not LiveTrialApplication assert fixture.controls.params.get('AutomaticSteeringLiveTrials') is None