PRODUCTAGENTharrisonhjohnson/productagent · /harnesses/night-orders/ops/capture/watchdog-absence.sh
000%

watchdog-absence.sh

view on github ↗78 lines · bash
#!/bin/bash
# Ventures absence watchdog — hourly (com.ventures.capture-watchdog, StartInterval 3600,
# RunAtLoad true). Law 4: the alarm lives outside the thing it watches.
#
# Charter v2 (variable presence): alarms are OPPORTUNITY-BASED, never calendar-based.
# Raw days-of-silence cannot distinguish deliberate rest from a dead system, and a month
# of variable presence is full of deliberate rest. Two conditions alarm; everything else is legal silence:
#   1. POLLER DEAD — this watchdog observes a live AC window right now, but the poller
#      (10-min interval) hasn't stamped last-ac today. Caught within the hour.
#   2. WRAPPER HUNG — a lane was invoked on a previous day (attempts file) but its
#      run-state never concluded that day. Caught next day.
# A closed laptop in a drawer for a week alarms no one.
# WATCH_*/CAPTURE_DIR env overrides exist for forced tests only.
set -u
export PATH="/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin"

VENT="$HOME/ventures"
CAP="${CAPTURE_DIR:-$VENT/00-ops/capture}"
TODAY="$(date +%F)"
mkdir -p "$CAP"

alert() { # stamp-key alert-file title body...
  local key="$1" alert_file="$2" title="$3"; shift 3
  local stamp="$CAP/last-absence-alert-$key"
  [ "$(cat "$stamp" 2>/dev/null)" = "$TODAY" ] && return 1   # one alert per key per day
  { echo "# $title — $TODAY (absence watchdog)"; echo
    for line in "$@"; do echo "$line"; done; echo
    echo "Poller log: $CAP/logs/poller.log"
    echo "_Hourly opportunity-based watchdog (capture/watchdog-absence.sh). Delete once acknowledged._"
  } >"$alert_file"
  echo "$TODAY" >"$stamp"
  /usr/bin/osascript -e "display notification \"$title\" with title \"Ventures capture\" subtitle \"$TODAY\" sound name \"Basso\"" >/dev/null 2>&1
  return 0
}

rc=0

# ---- 1. poller-dead: live window observed, poller not stamping -----------------
# Only meaningful on AC (the poller always stamps last-ac when on AC). Require >30 min
# of uptime so a fresh boot's RunAtLoad race can't false-alarm.
uptime_secs() {
  local boot; boot="$(sysctl -n kern.boottime 2>/dev/null | sed -E 's/.*sec = ([0-9]+).*/\1/')"
  [ -n "$boot" ] && echo $(( $(date +%s) - boot )) || echo 0
}
if pmset -g batt | grep -q "AC Power" && [ "$(uptime_secs)" -gt 1800 ]; then
  if [ "$(cat "$CAP/last-ac" 2>/dev/null)" != "$TODAY" ] && [ ! -f "$CAP/PAUSED" ]; then
    alert poller "$CAP/ALERT.md" "POLLER DEAD" \
      "The machine is on AC power right now, but the 10-min poller has not stamped last-ac today." \
      "Either launchd job com.ventures.capture is unloaded/broken, or poller.sh is failing before the stamp." \
      "Check: launchctl list | grep ventures.capture" && rc=1
  fi
fi

# ---- 2. wrapper-hung: invoked on a prior day, never concluded ------------------
check_lane() { # lane state-file alert-file title
  local lane="$1" state="$2" alert_file="$3" title="$4"
  local att_date="" att_n="" run_date=""
  read -r att_date att_n 2>/dev/null <"$CAP/$lane.attempts" || true
  [ -n "$att_date" ] || return 0
  [ "$att_date" = "$TODAY" ] && return 0                    # today's run may still be going
  run_date="$(jq -r '.date // empty' "$state" 2>/dev/null)"
  [ "$run_date" = "$att_date" ] && return 0                  # invoked and concluded — healthy
  # concluded later than invoked is also healthy (state date sorts after attempts date)
  [ -n "$run_date" ] && [[ "$run_date" > "$att_date" ]] && return 0
  alert "$lane" "$alert_file" "$title" \
    "The $lane lane was invoked on $att_date (attempt $att_n) but its run-state never concluded that day (state dated ${run_date:-never})." \
    "The wrapper is hanging or dying before writing state." \
    "State: $state" && return 1
  return 0
}

check_lane night \
  "${WATCH_NIGHT_STATE:-$VENT/00-ops/night/run-state.json}" \
  "${WATCH_NIGHT_ALERT:-$VENT/00-ops/night/ALERT.md}" "NIGHT WRAPPER HUNG" || rc=1
check_lane day \
  "${WATCH_DAY_STATE:-$VENT/00-ops/day/run-state.json}" \
  "${WATCH_DAY_ALERT:-$VENT/00-ops/day/ALERT.md}" "DAY WRAPPER HUNG" || rc=1
exit "$rc"
flow-designglobal
SKILL.md74 lines
SKILL.md162 lines
pm-strategistglobal
pm-strategist.md51 lines
todosystem
README.md44 lines
night-orderssystem
ops03 items
capture02 items
poller.sh89 lines
README.md84 lines
loops-spec.md212 lines
settings.json116 lines
README.md59 lines