mirror of
https://github.com/kuhyx/screen-locker.git
synced 2026-11-10 19:07:16 +01:00
The by-minutes bonus path clamped at 24:00 (_MIDNIGHT) and relied on
adjust_shutdown_schedule.sh's RESTORE_CEILING (23:00). It now clamps at
earned_time.shutdown_ceiling_for(day) via the new _ceiling.ceiling, so a
moved WAKE_MINUTES cannot be passed by a bonus. The shell's 1380 stays
the hard upper bound; ceiling() is min()'d with it, and a test pins the
Python constant to the script's value. _preview_sim and the invariant
test reuse that constant instead of a second 23 * 60.
Failure mode: if the earned_time lookup raises (ArithmeticError,
LookupError, TypeError, ValueError), ceiling() falls back to 23:00
loudly (logger.warning), never silently and never above the helper's
bound. That is fail-open relative to a lowered ceiling, bounded by the
shell clamp; an earned_time without the API (0.3.0) uses its constant.
Red first: new tests (21:00 ceiling, failing lookup, caps-at-ceiling)
failed on c3a5d14, pass now. 14-day replay of the real ledgers
(2026-10-18..31) through old and new code, plus an all-done-at-22:30
counterfactual: identical, every day 23:00; live files unchanged.
TODO-cap-bonus-at-day-ceiling.md removed (done).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WzxdWYdwRAtMg7ki6TKxuU
40 lines
1.3 KiB
Python
40 lines
1.3 KiB
Python
"""The latest shutdown a day can earn: ``earned_time``'s ceiling, bounded at 23:00."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import logging
|
|
from typing import TYPE_CHECKING
|
|
|
|
import earned_time
|
|
|
|
if TYPE_CHECKING:
|
|
from datetime import date
|
|
|
|
_logger = logging.getLogger(__name__)
|
|
|
|
# adjust_shutdown_schedule.sh's RESTORE_CEILING (23:00): the hard upper bound
|
|
# every --restore write is clamped to, and the answer when the lookup fails.
|
|
HARD_CEILING_MINUTES = 23 * 60
|
|
|
|
|
|
def ceiling(day: date) -> int:
|
|
"""The latest shutdown ``day`` can earn, minutes after its midnight.
|
|
|
|
Never above :data:`HARD_CEILING_MINUTES`. A lookup that raises fails open
|
|
to that bound -- the same cap the helper enforces anyway -- and says so at
|
|
warning, so a broken registry cannot silently lift or lower the cap.
|
|
"""
|
|
fn = getattr(earned_time, "shutdown_ceiling_for", None)
|
|
if fn is None:
|
|
return min(HARD_CEILING_MINUTES, int(earned_time.SHUTDOWN_CEILING_MINUTES))
|
|
try:
|
|
return min(HARD_CEILING_MINUTES, int(fn(day)))
|
|
except (ArithmeticError, LookupError, TypeError, ValueError) as err:
|
|
_logger.warning(
|
|
"shutdown ceiling for %s unavailable (%s); using %s",
|
|
day,
|
|
err,
|
|
f"{HARD_CEILING_MINUTES // 60:02d}:{HARD_CEILING_MINUTES % 60:02d}",
|
|
)
|
|
return HARD_CEILING_MINUTES
|