ProcessAlarm.skip_event() read event.alarmId on every EventClass.ALARM event,
but LidMalfunctionActivated (a sibling of LidAlarmActivated in that class) has
no alarmId, so a malfunction alarm crashed the sync with AttributeError.
- Narrow with isinstance before reading alarmId; malfunction events now upload
as "Malfunction" as intended, and sync continues.
- Type the alarm handlers against an explicit AlarmEvent union and add an
assert_never exhaustiveness guard, so a type checker rejects unguarded
subtype attribute access and flags any newly added ALARM event type.
- Fix a latent None-leak: alarm_to_nsentry now always returns a dict.
- Add mypy as a gradual-typing beachhead (setup.cfg [mypy], CI step, Pipfile
typecheck script), scoped to process_alarm.py. This configuration fails on
exactly the #156 class of bug.
- Add regression tests: malfunction processing, mixed alarm batches, the event
shape, and an AlarmEvent/EventClass.ALARM sync guard.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LpMTd5LzkqFLZTUKd3H8em