screen-locker/scripts/finish_2026_08_24.sh
Krzysztof kuhy Rudnicki 16ada6e5a2 Tell "could not check" apart from "no workout", and stop double-granting bonuses
A real 1h57m workout on 2026-08-24 synced to Firebase and the locker still
locked the screen. The trigger was auth: screen_locker was the only app with no
cached firebase_auth.json, so it fell back to a stale shared password and got
HTTP 400 every run. The defect was what it did next — an unreadable primary
source was reported as "no workout" rather than "could not check", which is the
same conflation that took 2026-06-12.

Recover the session by borrowing a live refresh token from a sibling app, track
degraded sources as state instead of a log line, and say so on the lock screen:
name the backend, the error, and how stale the data is, so a sync fault reads
as a sync fault.

Also fix two bugs found while finishing the recovery:

restore_and_bonus.py banked `existing + bonus` with no idempotence guard while
finish_2026_08_24.sh advertised "safe to re-run" — a second run silently
granted the compensation hours again. Guard on a durable
`shutdown_bonus_granted_for` list; the bonus map alone cannot answer the
question, since a week's total mixes earned streak hours with corrections.

_extra_benefits._save_state rebuilt state from four hardcoded keys, so the next
weekly rollover would have dropped that guard and re-armed the double-grant.
Merge onto loaded state instead.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EJmPLyT8NU4DzDJPV1KEX5
2026-08-24 13:07:20 +02:00

104 lines
2.8 KiB
Bash
Executable File

#!/bin/bash
# ============================================================================
# Finish the 2026-08-24 "workout not found" recovery, end to end.
#
# Everything Claude could not run itself, in the one order that works. The
# ordering is not cosmetic: `sick_day` fires BEFORE `already_logged` in the
# lock chain, so while the sick day stands it short-circuits the decision and
# hides whether the workout credit actually landed. Credit first, revoke
# second, re-arm last.
#
# Safe to re-run: every step is idempotent, and the script stops at the first
# failure rather than pressing on to re-arm a locker that cannot see today's
# workout.
#
# Usage:
# ./scripts/finish_2026_08_24.sh # do everything, then re-arm
# ./scripts/finish_2026_08_24.sh --no-arm # ...but leave enforcement off
# ============================================================================
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
readonly SCRIPT_DIR
readonly PY="$SCRIPT_DIR/.venv/bin/python"
readonly TARGET_DATE="2026-08-24"
ARM=1
usage() {
echo "Usage: $(basename "$0") [--no-arm]"
echo " --no-arm Run every step but leave enforcement disarmed"
exit 0
}
step() {
echo ""
echo "=============================================================="
echo " $1"
echo "=============================================================="
}
validate_requirements() {
if [[ ! -x $PY ]]; then
echo "Error: no venv interpreter at $PY — run ./run.sh once first" >&2
exit 1
fi
}
main() {
validate_requirements
cd "$SCRIPT_DIR"
step "1/7 Test suite (must be green before touching live state)"
"$PY" -m pytest screen_locker/tests/ -q --no-cov
step "2/7 Lint + type gate"
"$PY" -m pre_commit run --all-files
step "3/7 Sync: recover Firebase, then ingest today's workout"
# --sync-only never locks, so this is safe to run while disarmed.
./run.sh --sync-only
step "4/7 Did $TARGET_DATE actually land in the log?"
# Gate, don't warn: if the credit is not on disk there is nothing to
# revoke a sick day against, and re-arming here would lock you out again.
"$PY" scripts/verify_workout_credited.py --date "$TARGET_DATE"
step "5/7 Restore shutdown hours + grant the bonus"
"$PY" scripts/restore_and_bonus.py --date "$TARGET_DATE"
step "6/7 Revoke the sick days that this workout disproves"
"$PY" scripts/revoke_sick_day.py --date "$TARGET_DATE" --date 2026-06-12
if ((ARM)); then
step "7/7 Re-arm enforcement"
./arm.sh --rearm
else
step "7/7 Skipped re-arm (--no-arm); enforcement stays OFF"
echo "Re-arm later with: ./arm.sh --rearm"
fi
echo ""
echo "✓ Done."
}
while [[ $# -gt 0 ]]; do
case $1 in
--no-arm)
ARM=0
shift
;;
-h | --help)
usage
;;
*)
echo "Unknown option: $1" >&2
exit 1
;;
esac
done
main "$@"