screen-locker/screen_locker/_workday_penalty.py
Krzysztof kuhy Rudnicki 584177ec2e
Some checks are pending
dependency freshness / dependencies on newest stable (push) Waiting to run
md-naming / markdown naming convention (push) Waiting to run
pre-commit / pre-commit (push) Waiting to run
Tests / test (push) Waiting to run
Tests / lock-popup-safety (push) Waiting to run
Tests / flutter-coverage (push) Waiting to run
Tests / web-coverage (push) Waiting to run
release-apk / release (APK) (push) Waiting to run
Lose Tue/Wed/Thu leniency when wake-alarm signals a missed prior-day ring
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-23 21:23:31 +02:00

50 lines
1.7 KiB
Python

"""Read the workday-stick penalty wake-alarm signed for today.
A missed Tuesday/Wednesday/Thursday wake-alarm ring costs tomorrow's
relaxed-day leniency (:func:`screen_locker._weekly_check.is_relaxed_day`)
instead of just resetting to normal. This module never re-derives that
policy: the file carries a signed ``penalty_date``, and the only decision
here is *HMAC ok, dated today*. Anything else -- missing file, stale date,
bad signature -- is "no penalty", i.e. today's ordinary relaxed-day rule.
"""
from __future__ import annotations
import json
import logging
from typing import TYPE_CHECKING
from gatelock.log_integrity import verify_entry_hmac
from screen_locker._constants import WORKDAY_PENALTY_FILE
from screen_locker._day import today_str
if TYPE_CHECKING:
from datetime import datetime
_logger = logging.getLogger(__name__)
def _read_verified() -> dict[str, object] | None:
"""The file's entry when it exists, parses and verifies; else None."""
if not WORKDAY_PENALTY_FILE.exists():
_logger.debug("No workday penalty file at %s", WORKDAY_PENALTY_FILE)
return None
try:
entry = json.loads(WORKDAY_PENALTY_FILE.read_text())
except (OSError, json.JSONDecodeError) as exc:
_logger.warning("Cannot read %s: %s", WORKDAY_PENALTY_FILE, exc)
return None
if not isinstance(entry, dict) or not verify_entry_hmac(entry):
_logger.warning("Workday penalty file is not a signed object")
return None
return entry
def workday_penalty_today(now: datetime | None = None) -> bool:
"""Whether today lost its relaxed-day leniency to a missed prior ring."""
entry = _read_verified()
if entry is None:
return False
return entry.get("penalty_date") == today_str(now)