mirror of
https://github.com/kuhyx/testsAndMisc.git
synced 2026-11-10 20:27:02 +01:00
i3blocks shutdown: list unearned earners (+🧩1h ... →max 23:00)
The shutdown block now shows what can still push shutdown later, like the gaming-budget block: earners with earned_seconds == 0 from the enforcer's /api/budget, with a ceiling note once they overflow 23:00. No hint in the override/SHUTDOWN states or when the server is down. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014qWnrvoCawLZWUpTLJxHue
This commit is contained in:
parent
27be07db48
commit
c5aa20a085
1
.github/workflows/shell-tests.yml
vendored
1
.github/workflows/shell-tests.yml
vendored
@ -32,6 +32,7 @@ jobs:
|
||||
tests=(
|
||||
test_i3blocks_gaming_budget.sh
|
||||
test_i3blocks_persist_common.sh
|
||||
test_i3blocks_shutdown_hint.sh
|
||||
test_setup_dns_blocker.sh
|
||||
)
|
||||
failed=0
|
||||
|
||||
@ -0,0 +1,40 @@
|
||||
{
|
||||
"intent": "The i3 shutdown block shows which earners can still push shutdown later, like the gaming-budget block does: '⏻ 22:00 +🧩1h +📖1h +🗂30m +⚙30m →max 23:00'.",
|
||||
"scope": [
|
||||
"linux_configuration/i3blocks/shutdown_countdown.sh",
|
||||
"linux_configuration/tests/test_i3blocks_shutdown_hint.sh",
|
||||
"linux_configuration/tests/test_i3blocks_efficiency.sh",
|
||||
".github/workflows/shell-tests.yml",
|
||||
"Non-goal: no hint in the override or SHUTDOWN states; no change to how shutdown is computed (screen-locker owns that)"
|
||||
],
|
||||
"changes": [
|
||||
"earner_hint reads the enforcer's /api/budget .rules.earners and lists those with earned_seconds == 0, using bonus_seconds as the shutdown amount (equal for every earned_time earner today)",
|
||||
"'→max 23:00' is appended when the listed bonuses exceed the headroom to SHUTDOWN_CEILING_MINUTES (default 1380, earned_time's ceiling); no hint at all at the ceiling",
|
||||
"A dead or erroring server shows the bare time, which still comes from /etc/shutdown-schedule.conf",
|
||||
"Efficiency test stubs curl as a dead server and allows 2 execs (bash + curl); new behaviour test runs in the shell-tests CI job"
|
||||
],
|
||||
"verification": [
|
||||
{
|
||||
"command": "bash linux_configuration/i3blocks/shutdown_countdown.sh (live enforcer, conf at 22:00)",
|
||||
"result": "pass",
|
||||
"evidence": "'⏻ 22:00 +🧩1h +📖1h +🗂30m +⚙30m →max 23:00'; after the LeetCode solve was applied: '⏻ 23:00'"
|
||||
},
|
||||
{
|
||||
"command": "bash linux_configuration/tests/test_i3blocks_shutdown_hint.sh",
|
||||
"result": "pass",
|
||||
"evidence": "All shutdown hint tests passed (listing, ceiling note, all-earned, 1h30 padding, unknown earner label, server down / ok:false / non-JSON)"
|
||||
},
|
||||
{
|
||||
"command": "bash linux_configuration/tests/test_i3blocks_efficiency.sh && bash linux_configuration/tests/test_i3blocks_gaming_budget.sh",
|
||||
"result": "pass",
|
||||
"evidence": "All i3blocks efficiency regression tests passed; All gaming_budget checks passed"
|
||||
}
|
||||
],
|
||||
"risks": [
|
||||
"If an earner's shutdown_minutes ever differs from its gaming_minutes, the hint shows the gaming amount; /api/budget does not expose shutdown amounts.",
|
||||
"The 23:00 ceiling is duplicated from earned_time as a default; a registry change needs SHUTDOWN_CEILING_MINUTES or this default updated."
|
||||
],
|
||||
"rollback": [
|
||||
"git revert this commit; the block shows the bare '⏻ HH:MM' again"
|
||||
]
|
||||
}
|
||||
@ -1,44 +0,0 @@
|
||||
{
|
||||
"intent": "Prune the accumulated per-commit evidence and contract artifacts, which were the largest single driver of tracked-file growth in the repo.",
|
||||
"scope": [
|
||||
"docs/superpowers/evidence - 455 tracked files down to 21",
|
||||
"docs/superpowers/contracts - 241 tracked files down to 21",
|
||||
"Non-goal: docs/superpowers/sessions, which is append-only and enforced by check_append_only_sessions.sh. Untouched.",
|
||||
"Non-goal: .hippo/, reported on separately rather than pruned in the same pass."
|
||||
],
|
||||
"changes": [
|
||||
"Kept the 20 most recently committed artifacts in each of evidence/ and contracts/, plus both template.json files, and removed the other 655.",
|
||||
"Selected by real git commit date (git log -1 --format=%cs per file) rather than by parsing filenames: the corpus mixes -2026-05, -20260604 and undated slugs, so a filename date filter would have silently mis-sorted ~48 files.",
|
||||
"Used a count cap rather than a time window because growth is bursty: 487 of the 695 artifacts were committed in 2026-08 alone, so even a 7-day window would have kept 393 files and bounded nothing going forward."
|
||||
],
|
||||
"verification": [
|
||||
{
|
||||
"command": "grep -c template prune_final.txt",
|
||||
"result": "pass",
|
||||
"evidence": "0 - neither template.json was in the prune list. Confirmed present on disk after the removal; the hooks tell users to copy them, so deleting one would have broken the documented workflow."
|
||||
},
|
||||
{
|
||||
"command": "git ls-files docs/superpowers/{evidence,contracts,sessions} | wc -l",
|
||||
"result": "pass",
|
||||
"evidence": "evidence 21, contracts 21 (20 kept + template each), sessions 3 unchanged. Arithmetic closes: 695 = 40 kept + 655 pruned."
|
||||
},
|
||||
{
|
||||
"command": "./meta/scripts/check_ai_evidence.sh; ./meta/scripts/check_agent_contract.sh; ./meta/scripts/check_append_only_sessions.sh",
|
||||
"result": "pass",
|
||||
"evidence": "All three exit 0 after the prune. Verified beforehand by grep that no tooling reads the historical corpus - every reference is either template.json or validation of files staged in the current commit."
|
||||
},
|
||||
{
|
||||
"command": "git ls-files | wc -l",
|
||||
"result": "pass",
|
||||
"evidence": "1993 -> 1340 tracked files. check_repo_size still reports ~1050 source files, correctly unchanged: docs/superpowers/ is excluded from that count by design."
|
||||
}
|
||||
],
|
||||
"risks": [
|
||||
"Historical artifacts are now reachable only through git history (git log --diff-filter=D -- docs/superpowers/). Accepted: nothing reads them programmatically and their review value decays within days.",
|
||||
"The 20-file cap is enforced by nothing - it is a one-off prune, so the directories will grow again at roughly one artifact per commit. A recurring prune or a hard cap would be the durable fix if this recurs."
|
||||
],
|
||||
"rollback": [
|
||||
"git revert the commit restores all 655 files, since this is a pure deletion with no accompanying logic change.",
|
||||
"After rollback, confirm the three superpowers hooks still exit 0 and that both template.json files are present."
|
||||
]
|
||||
}
|
||||
@ -8,6 +8,9 @@ set -euo pipefail
|
||||
SHUTDOWN_CONFIG=${SHUTDOWN_CONFIG:-/etc/shutdown-schedule.conf}
|
||||
SKIP_DATES_FILE=${SKIP_DATES_FILE:-/etc/shutdown-skip-dates}
|
||||
OVERRIDES_FILE=${OVERRIDES_FILE:-/etc/shutdown-schedule-overrides.conf}
|
||||
BUDGET_API=${GAMING_BUDGET_API:-http://127.0.0.1:8000/api/budget}
|
||||
# earned_time's SHUTDOWN_CEILING_MINUTES (23:00): no earner pushes past it.
|
||||
SHUTDOWN_CEILING_MINUTES=${SHUTDOWN_CEILING_MINUTES:-1380}
|
||||
|
||||
# Function to show error state in i3blocks and exit
|
||||
show_error() {
|
||||
@ -148,6 +151,31 @@ if override_match=$(find_override_covering "$shutdown_epoch_today"); then
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Earners not yet done today, rendered like gaming_budget.sh: " +🧩1h +📖1h".
|
||||
# The enforcer's /api/budget reports gaming seconds per earner; every
|
||||
# earned_time earner moves shutdown by the same amount, so that one source
|
||||
# serves both blocks. Past the earned_time shutdown ceiling the extra is lost,
|
||||
# hence the "→max" note. Server down = no hint: the time above is still right.
|
||||
earner_hint() {
|
||||
local headroom_seconds=$(((SHUTDOWN_CEILING_MINUTES - shutdown_time_minutes) * 60))
|
||||
local ceiling_hhmm budget_json
|
||||
((headroom_seconds > 0)) || return 0
|
||||
command -v curl >/dev/null 2>&1 && command -v jq >/dev/null 2>&1 || return 0
|
||||
budget_json=$(curl -fsS --max-time 2 "$BUDGET_API" 2>/dev/null) || return 0
|
||||
printf -v ceiling_hhmm '%02d:%02d' $((SHUTDOWN_CEILING_MINUTES / 60)) $((SHUTDOWN_CEILING_MINUTES % 60))
|
||||
jq -r --argjson headroom "$headroom_seconds" --arg ceiling "$ceiling_hhmm" '
|
||||
def bonus_hm: (. / 60 | round) as $m
|
||||
| if $m < 60 then "\($m)m"
|
||||
else "\($m / 60 | floor)h" + ($m % 60 | if . == 0 then "" else "\(.)" | if length < 2 then "0" + . else . end end) end;
|
||||
{workout: "💪", leetcode: "🧩", reading: "📖", anki: "🗂", automation: "⚙"} as $icon
|
||||
| select(.ok == true)
|
||||
| [.rules.earners[]? | select((.earned_seconds // 0) == 0 and .bonus_seconds > 0)]
|
||||
| select(length > 0)
|
||||
| " " + (map("+\($icon[.name] // .label)\(.bonus_seconds | bonus_hm)") | join(" "))
|
||||
+ (if (map(.bonus_seconds) | add) > $headroom then " →max \($ceiling)" else "" end)
|
||||
' <<<"$budget_json" 2>/dev/null || true
|
||||
}
|
||||
|
||||
minutes_until_shutdown=$((shutdown_time_minutes - current_time_minutes))
|
||||
|
||||
if [[ $minutes_until_shutdown -le 30 ]]; then
|
||||
@ -160,6 +188,6 @@ else
|
||||
color="#6272A4"
|
||||
fi
|
||||
|
||||
echo "⏻ $(format_hhmm "$shutdown_epoch_today")"
|
||||
echo "⏻ $(format_hhmm "$shutdown_epoch_today")$(earner_hint)"
|
||||
echo "⏻"
|
||||
echo "$color"
|
||||
|
||||
@ -55,6 +55,14 @@ count_execs() {
|
||||
grep -c 'execve(' "$log_file"
|
||||
}
|
||||
|
||||
# A dead budget server: shutdown_countdown.sh's earner hint must never reach
|
||||
# the real enforcer from a test, and with no hint it runs no jq either.
|
||||
cat >"$BIN_DIR/curl" <<'EOF'
|
||||
#!/bin/bash
|
||||
exit 7
|
||||
EOF
|
||||
chmod +x "$BIN_DIR/curl"
|
||||
|
||||
cat >"$BIN_DIR/pacman" <<'EOF'
|
||||
#!/bin/bash
|
||||
exit 1
|
||||
@ -190,7 +198,8 @@ assert_equals '⏻ 21:00' "$(printf '%s\n' "$shutdown_countdown_output" | sed -n
|
||||
'shutdown countdown should show the exact clock time of the next shutdown'
|
||||
assert_equals '#F1FA8C' "$(printf '%s\n' "$shutdown_countdown_output" | sed -n '3p')" \
|
||||
'shutdown countdown should show yellow for two hours or less remaining'
|
||||
assert_le "$(count_execs "$I3BLOCKS_DIR/shutdown_countdown.sh")" 1 \
|
||||
# bash itself plus the one curl for the earner hint; no date helpers.
|
||||
assert_le "$(count_execs "$I3BLOCKS_DIR/shutdown_countdown.sh")" 2 \
|
||||
'shutdown countdown should avoid date helpers in the hot path'
|
||||
|
||||
shutdown_window_epoch=$(epoch_utc '2026-05-01 21:15:00')
|
||||
|
||||
97
linux_configuration/tests/test_i3blocks_shutdown_hint.sh
Executable file
97
linux_configuration/tests/test_i3blocks_shutdown_hint.sh
Executable file
@ -0,0 +1,97 @@
|
||||
#!/bin/bash
|
||||
# Behaviour tests for shutdown_countdown.sh's earner hint: which earners are
|
||||
# listed, their amounts, the 23:00 ceiling note, and the states that show no
|
||||
# hint. curl is stubbed on PATH to serve a fixture, so nothing talks to the
|
||||
# real enforcer.
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)
|
||||
REPO_DIR=$(cd -- "$SCRIPT_DIR/.." && pwd)
|
||||
BLOCK="$REPO_DIR/i3blocks/shutdown_countdown.sh"
|
||||
TMP_DIR=$(mktemp -d)
|
||||
|
||||
cleanup() {
|
||||
rm -rf "$TMP_DIR"
|
||||
}
|
||||
trap cleanup EXIT
|
||||
|
||||
fail() {
|
||||
printf 'FAIL: %s\n' "$1" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
assert_eq() {
|
||||
if [[ "$1" != "$2" ]]; then
|
||||
fail "$3 (expected '$1', actual '$2')"
|
||||
fi
|
||||
}
|
||||
|
||||
printf '{}' | jq -e . >/dev/null 2>&1 ||
|
||||
fail 'a working jq is required for the shutdown hint tests (pacman -S jq)'
|
||||
|
||||
# Stub curl: print $FIXTURE, or fail like `curl -f` on a dead server.
|
||||
BIN_DIR="$TMP_DIR/bin"
|
||||
mkdir -p "$BIN_DIR"
|
||||
cat >"$BIN_DIR/curl" <<'STUB'
|
||||
#!/bin/bash
|
||||
[[ -r ${FIXTURE:-} ]] || exit 7
|
||||
cat "$FIXTURE"
|
||||
STUB
|
||||
chmod +x "$BIN_DIR/curl"
|
||||
|
||||
FIXTURE="$TMP_DIR/budget.json"
|
||||
SCHEDULE="$TMP_DIR/schedule.conf"
|
||||
NO_OVERRIDES="$TMP_DIR/overrides.conf"
|
||||
: >"$NO_OVERRIDES"
|
||||
# 2026-05-01 is a Friday, so THU_SUN_MINUTES applies.
|
||||
NOW=$(TZ=UTC date -d '2026-05-01 18:00:00' +%s)
|
||||
|
||||
# write_earners NAME:EARNED:BONUS ... -- the /api/budget earners list.
|
||||
write_earners() {
|
||||
local rows=() name earned bonus
|
||||
for spec in "$@"; do
|
||||
IFS=: read -r name earned bonus <<<"$spec"
|
||||
rows+=("{\"name\":\"$name\",\"label\":\"$name\",\"earned_seconds\":$earned,\"bonus_seconds\":$bonus}")
|
||||
done
|
||||
local IFS=,
|
||||
printf '{"ok":true,"rules":{"earners":[%s]}}\n' "${rows[*]}" >"$FIXTURE"
|
||||
}
|
||||
|
||||
# first_line SHUTDOWN_MINUTES -- the block's full_text at 18:00.
|
||||
first_line() {
|
||||
printf 'THU_SUN_MINUTES=%s\nMON_WED_MINUTES=%s\nMORNING_END_MINUTES=300\n' \
|
||||
"$1" "$1" >"$SCHEDULE"
|
||||
TZ=UTC NOW_EPOCH="$NOW" PATH="$BIN_DIR:$PATH" FIXTURE="$FIXTURE" \
|
||||
SHUTDOWN_CONFIG="$SCHEDULE" OVERRIDES_FILE="$NO_OVERRIDES" \
|
||||
SKIP_DATES_FILE=/dev/null bash "$BLOCK" | sed -n 1p
|
||||
}
|
||||
|
||||
printf 'Checking unearned earners are listed with icon and amount...\n'
|
||||
write_earners workout:7200:7200 leetcode:0:3600 reading:0:3600 anki:0:1800 automation:0:1800
|
||||
assert_eq '⏻ 20:00 +🧩1h +📖1h +🗂30m +⚙30m' "$(first_line 1200)" \
|
||||
'earned ones hidden, the rest listed; 3h fits under 23:00'
|
||||
|
||||
printf 'Checking the ceiling note once the bonuses overflow 23:00...\n'
|
||||
assert_eq '⏻ 22:00 +🧩1h +📖1h +🗂30m +⚙30m →max 23:00' "$(first_line 1320)" \
|
||||
'3h of bonuses with 1h of headroom'
|
||||
|
||||
printf 'Checking nothing is listed at the ceiling or with all earned...\n'
|
||||
assert_eq '⏻ 23:00' "$(first_line 1380)" 'no headroom, no hint'
|
||||
write_earners leetcode:3600:3600 reading:3600:3600
|
||||
assert_eq '⏻ 20:00' "$(first_line 1200)" 'everything earned'
|
||||
|
||||
printf 'Checking odd amounts and unknown earners...\n'
|
||||
write_earners leetcode:0:5400 newgate:0:900
|
||||
assert_eq '⏻ 20:00 +🧩1h30 +newgate15m' "$(first_line 1200)" \
|
||||
'1h30 padded like gaming_budget.sh; unknown earner falls back to label'
|
||||
|
||||
printf 'Checking a dead or unhappy server shows the bare time...\n'
|
||||
rm -f "$FIXTURE"
|
||||
assert_eq '⏻ 20:00' "$(first_line 1200)" 'server down'
|
||||
printf '{"ok":false}\n' >"$FIXTURE"
|
||||
assert_eq '⏻ 20:00' "$(first_line 1200)" 'budget error'
|
||||
printf 'not json\n' >"$FIXTURE"
|
||||
assert_eq '⏻ 20:00' "$(first_line 1200)" 'unreadable reply'
|
||||
|
||||
printf 'All shutdown hint tests passed.\n'
|
||||
Loading…
Reference in New Issue
Block a user