wake-alarm/scripts/vmbox/guest_cases.sh
Krzysztof kuhy Rudnicki c3c129b068 Make the morning session the carrot: earn 11:00 by finishing it in time
On 2026-09-19 the phone was powered off after the bathroom tag and nothing
on the PC noticed: every finished session was published and nothing read
it. Now wake-alarm distils the phone's morning into an HMAC-signed
~/.local/state/wake_alarm/morning_session.json (wake-alarm-session.timer,
07:00-11:00 every 2 min) that screen-locker and leetcode-guard read to
defer their locks -- provisionally until min(start + 90 min, 09:30) while
the session is live, until 11:00 once it completed inside that cutoff,
and not at all for a session finished later (the phone switched on at
noon completes the legs then; that must un-lock nothing).

The phone publishes the session at start as well as at finish
(current_session, cleared with the finish stamp), so the PC can tell
"started, not finished" from "never started". A required 1-5 energy tap
joins the weigh-in and ships as latest_energy with a PC reader mirroring
_weight (the shared half is _field_reader).

Also fixes the vmbox harness, red since the 2026-09-11 home-tidy: the
guest tree lives at ~/wake-alarm, and the INSTALL-OK grep matched vm's
echo of the command line. Takes Flutter 3.47.5.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Tp54ewdXRLaimUuT8LgYyn
2026-09-19 16:40:51 +02:00

108 lines
3.4 KiB
Bash
Executable File

#!/bin/bash
# ============================================================================
# wake_alarm vmbox harness — the cases that run INSIDE the guest.
#
# Kept as its own file rather than inlined into `vm run` for two reasons: the
# vmbox runner wraps its argument in `sh -c` and chokes on a multi-line
# command, and a real file gets shellcheck like every other script here.
#
# Each case prints "PASS <name>" or "FAIL <name>: <why>" and the script exits
# non-zero if any failed, so the host adjudicates on an exit code rather than
# by reading prose.
# ============================================================================
set -uo pipefail
readonly PY=/usr/bin/python3
FAILURES=0
pass() {
echo "PASS $1"
}
fail() {
echo "FAIL $1: $2"
FAILURES=$((FAILURES + 1))
}
# --- Case: the package installs and imports from SYSTEM python ---------------
# The units run /usr/bin/python directly, never a venv, so this is the check
# that actually matters (CLAUDE.md).
case_imports() {
local name="imports-from-system-python"
if "$PY" -c "import wake_alarm, gatelock" 2>/dev/null; then
pass "$name"
else
fail "$name" "wake_alarm or gatelock missing from /usr/bin/python3"
fi
}
# --- Case: the trigger timer is actually enabled -----------------------------
# install.sh writing the unit file is not the same as systemd running it, and
# a timer that is present but disabled fails silently every morning.
case_units() {
local timer state
for timer in wake-alarm-trigger.timer wake-alarm-session.timer; do
state="$(systemctl --user is-enabled "$timer" 2>&1)"
if [[ "$state" == "enabled" ]]; then
pass "${timer%.timer}-enabled"
else
fail "${timer%.timer}-enabled" "expected enabled, got '$state'"
fi
done
}
# --- Case: the session refresh runs without a backend ------------------------
# The guest has no Firebase config, so --refresh must degrade to "no verified
# morning session" (exit 1) rather than crash: that is what an unconfigured
# machine's timer does every two minutes.
case_session_refresh() {
local name="session-refresh-degrades" output
# Exit 1 is the expected verdict here ("nothing verifiable"), so capture
# first: piped straight into grep, pipefail would fail the case on it.
output="$("$PY" -m wake_alarm._session --refresh 2>/dev/null || true)"
if grep -q "no verified" <<<"$output"; then
pass "$name"
else
fail "$name" "--refresh did not degrade cleanly"
fi
}
# --- Case: the trigger decides without crashing ------------------------------
case_trigger_dry_run() {
local name="trigger-dry-run"
if "$PY" -m wake_alarm._trigger --dry-run 2>/dev/null | grep -q "firing"; then
pass "$name"
else
fail "$name" "--dry-run produced no decision line"
fi
}
main() {
case "${1:-all}" in
all)
case_imports
case_units
case_trigger_dry_run
case_session_refresh
;;
*)
echo "usage: $0 [all]" >&2
exit 2
;;
esac
echo "---"
if [[ $FAILURES -gt 0 ]]; then
echo "$FAILURES case(s) failed"
exit 1
fi
# The host greps for this exact string. `vm run` reports the VM's verdict
# rather than this script's exit code, so an exit status alone would let
# the gate fail open.
echo "ALL-CASES-PASSED"
}
main "$@"