← back to Dw Yolo Loop
scripts/staged-launchd/promote-canary.sh
70 lines
#!/bin/sh
# promote-canary (c34) — turn one of this run's loose read-only canary scripts
# into an install-ready package: a launchd plist + a heartbeat-writing run
# wrapper + a manifest.json row for the dw-canary-watchdog. GENERATES ONLY into
# scripts/staged-launchd/<label>/ — it NEVER bootstraps/installs (Steve-gated).
#
# Why: this run built ~8 one-off canaries that ran once and would otherwise rot —
# the exact silent-death pattern (a dead, unwatched check) that hid the 12-day
# pg_dump backup gap. Promotion makes each canary scheduled AND heartbeat-watched
# by the already-loaded dw-canary-meta-watchdog.
#
# Heartbeat convention (from dw-canary-watchdog/manifest.json, DTD-C 2026-06-15):
# stamp ~/.claude/heartbeats/<label>.stamp (authoritative) > data/latest.json mtime
#
# Usage: sh promote-canary.sh <label> <abs-script.mjs> <interval_seconds> [warn_after] [fail_after]
set -e
LABEL="$1"; SCRIPT="$2"; INTERVAL="$3"; WARN="${4:-$((INTERVAL*2))}"; FAIL="${5:-$((INTERVAL*3))}"
[ -z "$LABEL" ] || [ -z "$SCRIPT" ] || [ -z "$INTERVAL" ] && { echo "usage: promote-canary.sh <label> <script.mjs> <interval_s> [warn] [fail]"; exit 2; }
BASE="$(cd "$(dirname "$0")" && pwd)/$LABEL" # worktree-aware: write next to this script, not a hardcoded repo
mkdir -p "$BASE"
NODE="$(command -v node || echo /opt/homebrew/bin/node)"
# 1) run wrapper — runs the canary, then stamps the heartbeat on exit 0/2 (PASS/FAIL both = "it ran")
cat > "$BASE/run-$LABEL.sh" <<WRAP
#!/bin/sh
# auto-generated by promote-canary.sh — runs $LABEL then writes its heartbeat stamp
mkdir -p "\$HOME/.claude/heartbeats"
"$NODE" "$SCRIPT"; rc=\$?
# rc 0=PASS, 2=FAIL-verdict — both mean the canary actually executed → stamp it
if [ \$rc -eq 0 ] || [ \$rc -eq 2 ]; then date -u +%Y-%m-%dT%H:%M:%SZ > "\$HOME/.claude/heartbeats/$LABEL.stamp"; fi
exit \$rc
WRAP
chmod +x "$BASE/run-$LABEL.sh"
# 2) launchd plist (interval-based)
cat > "$BASE/com.steve.$LABEL.plist" <<PLIST
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0"><dict>
<key>Label</key><string>com.steve.$LABEL</string>
<key>ProgramArguments</key><array><string>/bin/sh</string><string>$BASE/run-$LABEL.sh</string></array>
<key>StartInterval</key><integer>$INTERVAL</integer>
<key>RunAtLoad</key><false/>
<key>StandardOutPath</key><string>/tmp/$LABEL.log</string>
<key>StandardErrorPath</key><string>/tmp/$LABEL.log</string>
</dict></plist>
PLIST
# 3) manifest.json row to add to ~/Projects/dw-canary-watchdog/manifest.json
cat > "$BASE/manifest-row.json" <<ROW
{
"label": "$LABEL",
"cadence": "every ${INTERVAL}s (StartInterval)",
"stamp": "~/.claude/heartbeats/$LABEL.stamp",
"artifact": "~/.claude/heartbeats/$LABEL.stamp",
"launchd_log": "/tmp/$LABEL.log",
"warn_after": $WARN,
"fail_after": $FAIL
}
ROW
# 4) the Steve-gated install one-liner
cat > "$BASE/INSTALL.txt" <<INST
# Steve-gated install for com.steve.$LABEL:
cp "$BASE/com.steve.$LABEL.plist" ~/Library/LaunchAgents/
launchctl bootstrap gui/\$(id -u) ~/Library/LaunchAgents/com.steve.$LABEL.plist
# then add manifest-row.json into ~/Projects/dw-canary-watchdog/manifest.json (canaries[])
INST
echo "staged: $BASE (plist + run wrapper + manifest row + INSTALL.txt)"