"""Pure SLC override contribution from qualified driver intent and current pedal evidence.""" from typing import cast import math from dataclasses import dataclass, replace from enum import Enum from openpilot.starpilot.speed_limits import acceptance as acc from openpilot.starpilot.speed_limits import action_arbitration as arb from openpilot.starpilot.speed_limits import speed_domain as sd SUSPENSION_NS = 750_000_000 class EvidenceKind(Enum): VALID = "valid" ABSENT = "absent" UNKNOWN = "unknown" STALE = "stale" @dataclass(frozen=True) class SelectedEvidence: kind: EvidenceKind speed_mps: float | None = None @dataclass(frozen=True) class PedalEvidence: kind: EvidenceKind pressed: bool | None = None ego_mps: float | None = None @dataclass(frozen=True) class Context: context_id: str issued_at_ns: int accepted_target_mps: float @dataclass(frozen=True) class State: session_id: str persistent_selected_mps: float | None = None accepted_mps: float | None = None context: Context | None = None next_context_id: int = 1 active: bool = False mode: acc.Mode | None = None owner: acc.LongitudinalOwner | None = None suspension_started_ns: int | None = None last_effect_id: int = -1 last_adoption_sequence: int = -1 last_timestamp_ns: int | None = None reset_required: bool = False effective_target_mps: float | None = None applied_selected_mps: float | None = None @dataclass(frozen=True) class EventReceipt: effect_id: int acknowledged: bool consumed: bool status: str @dataclass(frozen=True) class Decision: state: State contribution_mps: float | None = None basis: str = "none" context: Context | None = None event_receipt: EventReceipt | None = None errors: tuple[str, ...] = () def _token(value: object) -> bool: return type(value) is str and 0 < len(value) <= 256 and bool(value.strip()) and not any(ord(c) < 32 for c in value) def _integer(value: object, minimum: int = 0) -> bool: return type(value) is int and value >= minimum def _speed(value: object) -> bool: if type(value) not in (int, float): return False assert isinstance(value, (int, float)) try: return math.isfinite(value) and value > 0 except OverflowError: return False def _ego_speed(value: object) -> bool: if type(value) not in (int, float): return False assert isinstance(value, (int, float)) try: return math.isfinite(value) and value >= 0 except OverflowError: return False def _state_valid(state: object) -> bool: if not isinstance(state, State) or not _token(state.session_id): return False if any(v is not None and not _speed(v) for v in (state.persistent_selected_mps, state.accepted_mps, state.effective_target_mps, state.applied_selected_mps)): return False if (not _integer(state.next_context_id, 1) or type(state.active) is not bool or not _integer(state.last_effect_id, -1) or not _integer(state.last_adoption_sequence, -1) or type(state.reset_required) is not bool or any(v is not None and not _integer(v) for v in (state.suspension_started_ns, state.last_timestamp_ns))): return False if (state.mode is not None and not isinstance(state.mode, acc.Mode)) or (state.owner is not None and not isinstance(state.owner, acc.LongitudinalOwner)): return False if (state.mode is None) != (state.owner is None): return False if state.mode is not None and state.last_timestamp_ns is None: return False if state.reset_required and (state.persistent_selected_mps is not None or state.active or state.context is not None or state.suspension_started_ns is not None): return False if state.suspension_started_ns is not None and (state.active or state.last_timestamp_ns is None or state.suspension_started_ns > state.last_timestamp_ns or state.mode not in (acc.Mode.LONGITUDINAL_ONLY, acc.Mode.COMBINED) or state.owner is not acc.LongitudinalOwner.SYSTEM): return False # Retained intent is raw selected speed; a negative offset can make it lower # than the raw accepted limit while still exceeding the effective threshold. if state.context is not None: c = state.context if (not isinstance(c, Context) or not _token(c.context_id) or not _integer(c.issued_at_ns) or not _speed(c.accepted_target_mps) or state.last_timestamp_ns is None or c.issued_at_ns > state.last_timestamp_ns or not state.active or state.accepted_mps != c.accepted_target_mps or state.next_context_id <= 1 or c.context_id != f"override:{state.next_context_id - 1}" or state.mode not in (acc.Mode.LONGITUDINAL_ONLY, acc.Mode.COMBINED) or state.owner is not acc.LongitudinalOwner.SYSTEM): return False if state.active and (state.context is None or state.suspension_started_ns is not None): return False if state.active and state.effective_target_mps is None: return False return True def new_session(session_id: str) -> State: state = State(session_id) if not _state_valid(state): raise ValueError("invalid session ID") return state def _selected_valid(selected: object) -> bool: return isinstance(selected, SelectedEvidence) and isinstance(selected.kind, EvidenceKind) and ( _speed(selected.speed_mps) if selected.kind is EvidenceKind.VALID else selected.speed_mps is None) def _pedal_valid(pedal: object) -> bool: return isinstance(pedal, PedalEvidence) and isinstance(pedal.kind, EvidenceKind) and ( (type(pedal.pressed) is bool and ((pedal.ego_mps is None or _ego_speed(pedal.ego_mps)) if not pedal.pressed else _ego_speed(pedal.ego_mps))) if pedal.kind is EvidenceKind.VALID else pedal.pressed is None and pedal.ego_mps is None) def _change_valid(change: object) -> bool: return (isinstance(change, arb.ClassifiedChange) and _token(change.session_id) and _integer(change.effect_id) and (change.action_id is None or _integer(change.action_id)) and (change.context_id is None or _token(change.context_id)) and (change.started_at_ns is None or _integer(change.started_at_ns)) and (change.previous_mps is None or _speed(change.previous_mps)) and _speed(change.selected_mps) and isinstance(change.disposition, arb.Disposition)) def _acceptance_valid(decision: object, authority: object, policy: object, session_id: str, now_ns: int) -> bool: if (not isinstance(decision, acc.Decision) or not isinstance(decision.state, acc.State) or decision.errors or decision.state.session_id != session_id or decision.state.last_timestamp_ns != now_ns or not isinstance(authority, acc.Authority) or not isinstance(policy, acc.Policy) or decision.authority != authority or decision.policy != policy or not acc.observation_is_valid(decision.state.observation, session_id=session_id)): return False if policy.display_only or not authority.can_control: return decision.control_target_mps is None if decision.control_target_mps is None: return True accepted = decision.state.accepted if (accepted is None or not _token(accepted.session_id) or not _speed(accepted.candidate.speed_mps) or decision.control_target_mps != accepted.candidate.speed_mps): return False return decision.state.observation.kind is acc.ObservationKind.VALID or acc.previous_limit_is_qualified(decision) def step(state: State, acceptance: acc.Decision, authority: acc.Authority, policy: acc.Policy, selected: SelectedEvidence, pedal: PedalEvidence, *, now_ns: int, change: arb.ClassifiedChange | None = None, domain: sd.DomainResolution | None = None) -> Decision: """Propose an SLC contribution, never an actuator or stock-button command.""" if not _state_valid(state): return Decision(state, errors=("invalid override state",)) if state.reset_required: return Decision(state, basis="session_reset_required", errors=("new session required",)) if (not _integer(now_ns) or (state.last_timestamp_ns is not None and now_ns < state.last_timestamp_ns) or not _selected_valid(selected) or not _pedal_valid(pedal) or (change is not None and not _change_valid(change)) or (domain is not None and (not isinstance(domain, sd.DomainResolution) or not isinstance(domain.status, sd.DomainStatus)))): failed = replace(state, persistent_selected_mps=None, context=None, active=False, suspension_started_ns=None, reset_required=True) return Decision(failed, basis="session_reset_required", errors=("invalid evidence or monotonic clock",)) old = state state = replace(state, last_timestamp_ns=now_ns) receipt = None if change is not None: if change.session_id != state.session_id: receipt = EventReceipt(change.effect_id, True, False, "foreign_session") elif change.effect_id <= state.last_effect_id: receipt = EventReceipt(change.effect_id, True, False, "already_seen") else: state = replace(state, last_effect_id=change.effect_id) receipt = EventReceipt(change.effect_id, True, False, "ineligible") if not _acceptance_valid(acceptance, authority, policy, state.session_id, now_ns): failed = replace(state, persistent_selected_mps=None, active=False, context=None, suspension_started_ns=None, reset_required=True) return Decision(failed, basis="session_reset_required", event_receipt=receipt, errors=("acceptance authority, policy, target or clock mismatch",)) assert isinstance(acceptance, acc.Decision) and isinstance(acceptance.state, acc.State) assert isinstance(authority, acc.Authority) and isinstance(policy, acc.Policy) accepted = acceptance.state.accepted accepted_speed = accepted.candidate.speed_mps if accepted is not None else None domain_unavailable = False selected_delta = 0.0 effective_target = (old.effective_target_mps if acceptance.control_target_mps is None and accepted_speed == old.accepted_mps else acceptance.control_target_mps) if domain is not None: if domain.status is sd.DomainStatus.INVALID: failed = replace(state, persistent_selected_mps=None, active=False, context=None, suspension_started_ns=None, reset_required=True) return Decision(failed, basis="session_reset_required", event_receipt=receipt, errors=("invalid speed domain",)) if domain.status is not sd.DomainStatus.VALID: if domain.context is not None: failed = replace(state, persistent_selected_mps=None, active=False, context=None, suspension_started_ns=None, reset_required=True) return Decision(failed, basis="session_reset_required", event_receipt=receipt, errors=("invalid speed domain",)) domain_unavailable = True else: c = domain.context if (not isinstance(c, sd.DomainContext) or not sd.context_is_valid(c) or c.session_id != state.session_id or c.timestamp_ns != now_ns or c.accepted_raw_mps != acceptance.control_target_mps or selected.speed_mps != c.selected_raw_mps or (pedal.pressed and pedal.ego_mps != c.ego_raw_mps) or not _speed(c.effective_cluster_mps)): failed = replace(state, persistent_selected_mps=None, active=False, context=None, suspension_started_ns=None, reset_required=True) return Decision(failed, basis="session_reset_required", event_receipt=receipt, errors=("speed domain mismatch",)) selected_delta = c.selected_delta_mps effective_target = c.effective_cluster_mps mode_changed = old.mode is not None and (old.mode != authority.mode or old.owner != authority.owner) if acceptance.history_write is not None: state = replace(state, persistent_selected_mps=None, applied_selected_mps=None) comparison_target = state.applied_selected_mps + selected_delta if state.applied_selected_mps is not None else effective_target if (not domain_unavailable and state.persistent_selected_mps is not None and effective_target is not None and selected.speed_mps is not None and math.isclose(cast(float, comparison_target), selected.speed_mps + selected_delta, abs_tol=0.001, rel_tol=0)): state = replace(state, persistent_selected_mps=None) state = replace(state, accepted_mps=accepted_speed, effective_target_mps=effective_target, mode=authority.mode, owner=authority.owner) adoption = acceptance.adoption adopted_now = False if adoption is not None and adoption.session_id == state.session_id and adoption.clear_override and acceptance.adoption_receipt is not None: if (acceptance.adoption_receipt.consumed and acceptance.adoption_receipt.sequence_id == adoption.action_sequence_id and adoption.action_sequence_id > state.last_adoption_sequence): state = replace(state, persistent_selected_mps=None, last_adoption_sequence=adoption.action_sequence_id) adopted_now = True eligible = (authority.can_control and not policy.display_only and accepted_speed is not None and effective_target is not None and selected.kind is EvidenceKind.VALID and pedal.kind is EvidenceKind.VALID and (not domain_unavailable or acceptance.control_target_mps is None)) if not eligible: state = replace(state, active=False, context=None, suspension_started_ns=None) return Decision(state, basis="display_only" if policy.display_only else "inactive_or_missing_evidence", event_receipt=receipt) target = accepted_speed assert target is not None and _speed(target) and selected.speed_mps is not None and pedal.pressed is not None state = replace(state, suspension_started_ns=None) rotate = (not old.active or old.context is None or old.context.accepted_target_mps != target or old.effective_target_mps != effective_target or mode_changed or adopted_now) if rotate: context = Context(f"override:{state.next_context_id}", now_ns, target) state = replace(state, context=context, next_context_id=state.next_context_id + 1, active=True) else: state = replace(state, active=True) if receipt is not None and receipt.status == "ineligible" and change is not None: if policy.display_only: receipt = replace(receipt, status="display_only") elif (change.disposition is arb.Disposition.DRIVER_INTENT and change.action_id is not None and change.previous_mps is not None and change.previous_mps != change.selected_mps and old.active and old.context is not None and not rotate and change.context_id == old.context.context_id and change.started_at_ns is not None and old.context.issued_at_ns <= change.started_at_ns <= now_ns and change.selected_mps == selected.speed_mps): state = replace(state, persistent_selected_mps=(change.selected_mps if comparison_target is not None and not math.isclose(change.selected_mps + selected_delta, comparison_target, abs_tol=0.001, rel_tol=0) else None)) receipt = replace(receipt, consumed=True, status="driver_intent") else: receipt = replace(receipt, status="unqualified_effect") if state.persistent_selected_mps is not None: return Decision(state, state.persistent_selected_mps, "persistent", state.context, receipt) return Decision(state, basis="adoption_clear" if adopted_now else "none", context=state.context, event_receipt=receipt)