← back to Trending Dw
trending: 10-min health+opportunity tick + launchd cron (DTD verdict A)
4fe938fa9ee1baf92576fa028a2113437962e030 · 2026-07-06 21:09:33 -0700 · Steve Abrams
- scripts/tick.sh: deterministic $0 watcher — probes live uptime, measures data
staleness, detects ComfyUI; drafts a GATED refresh memo when stale>threshold (7d)
and an image-gap opportunity memo when ComfyUI returns. Idempotent (find-based guard).
NEVER auto-deploys, NEVER auto-spends (metered Exa refresh stays Steve-gated).
- ops/com.steve.trending-dw-tick.plist: StartInterval 600 (every 10 min), RunAtLoad.
- resolves node absolutely (launchd minimal-PATH fix) + hardened PATH.
- ledger at data/tick-ledger.jsonl (gitignored); state in data/tick-state/.
Files touched
M .gitignoreA ops/com.steve.trending-dw-tick.plistA scripts/tick.sh
Diff
commit 4fe938fa9ee1baf92576fa028a2113437962e030
Author: Steve Abrams <steve@designerwallcoverings.com>
Date: Mon Jul 6 21:09:33 2026 -0700
trending: 10-min health+opportunity tick + launchd cron (DTD verdict A)
- scripts/tick.sh: deterministic $0 watcher — probes live uptime, measures data
staleness, detects ComfyUI; drafts a GATED refresh memo when stale>threshold (7d)
and an image-gap opportunity memo when ComfyUI returns. Idempotent (find-based guard).
NEVER auto-deploys, NEVER auto-spends (metered Exa refresh stays Steve-gated).
- ops/com.steve.trending-dw-tick.plist: StartInterval 600 (every 10 min), RunAtLoad.
- resolves node absolutely (launchd minimal-PATH fix) + hardened PATH.
- ledger at data/tick-ledger.jsonl (gitignored); state in data/tick-state/.
---
.gitignore | 3 +
ops/com.steve.trending-dw-tick.plist | 13 +++++
scripts/tick.sh | 108 +++++++++++++++++++++++++++++++++++
3 files changed, 124 insertions(+)
diff --git a/.gitignore b/.gitignore
index 421f4d2..982b07b 100644
--- a/.gitignore
+++ b/.gitignore
@@ -9,3 +9,6 @@ build/
data/shopify-queue.json
.gapvenv/
+
+data/tick-state/
+data/tick-ledger.jsonl
diff --git a/ops/com.steve.trending-dw-tick.plist b/ops/com.steve.trending-dw-tick.plist
new file mode 100644
index 0000000..8c0075b
--- /dev/null
+++ b/ops/com.steve.trending-dw-tick.plist
@@ -0,0 +1,13 @@
+<?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.trending-dw-tick</string>
+ <key>ProgramArguments</key><array>
+ <string>/bin/bash</string>
+ <string>/Users/macstudio3/Projects/trending-dw/scripts/tick.sh</string>
+ </array>
+ <key>StartInterval</key><integer>600</integer>
+ <key>RunAtLoad</key><true/>
+ <key>StandardOutPath</key><string>/tmp/trending-dw-tick.log</string>
+ <key>StandardErrorPath</key><string>/tmp/trending-dw-tick.log</string>
+</dict></plist>
diff --git a/scripts/tick.sh b/scripts/tick.sh
new file mode 100755
index 0000000..61d52e8
--- /dev/null
+++ b/scripts/tick.sh
@@ -0,0 +1,108 @@
+#!/bin/bash
+# trending-dw — 10-minute HEALTH + OPPORTUNITY tick.
+# DTD verdict (2026-07-06, 3/3 option A): this cron is a WATCHER, not a writer/spender.
+# • probe live uptime
+# • measure data staleness
+# • detect ComfyUI availability (for the 13 image-gap fills)
+# • when a threshold is crossed, DRAFT a GATED approval memo to pending-approval/ (idempotent)
+# HARD RULE: never auto-deploy, never auto-spend (Exa refresh is metered), never write to prod.
+# Everything here is $0 local — read-only probes + local file writes to the approval queue.
+set -u
+cd "$(dirname "$0")/.."
+ROOT="$(pwd)"
+# launchd runs with a minimal PATH (no /opt/homebrew/bin) — resolve node absolutely
+# or the data-staleness read silently returns empty. Belt: prepend homebrew to PATH too.
+export PATH="/opt/homebrew/bin:/usr/local/bin:$PATH"
+NODE="$(command -v node || echo /opt/homebrew/bin/node)"
+STATE="$ROOT/data/tick-state"; mkdir -p "$STATE"
+LEDGER="$ROOT/data/tick-ledger.jsonl"
+PENDING="$HOME/.claude/yolo-queue/pending-approval"; mkdir -p "$PENDING"
+NOW="$(date +%Y-%m-%dT%H:%M:%S%z)"
+DAY="$(date +%Y-%m-%d)"
+
+LIVE_URL="${TRENDING_LIVE_URL:-https://trending.designerwallcoverings.com/}"
+AUTH="${TRENDING_AUTH:-admin:DW2024!}"
+STALE_THRESHOLD="${TRENDING_STALE_DAYS:-7}" # weekly refresh cadence (matches the original staged plist)
+
+# ── 1. live uptime probe (read-only) ──
+HTTP="$(curl -s -o /dev/null -w '%{http_code}' -u "$AUTH" --max-time 12 "$LIVE_URL" 2>/dev/null || echo 000)"
+[ "$HTTP" = "200" ] && UP=1 || UP=0
+
+# ── 2. data staleness (read local snapshot) ──
+read AS_OF STALE_DAYS IMG_GAP COUNT <<<"$("$NODE" -e '
+ try{ const d=JSON.parse(require("fs").readFileSync("data/bestsellers.json","utf8"));
+ const its=d.items||[]; const sp=its.map(i=>i.spottedAt).filter(Boolean).sort();
+ const asOf=sp.length?sp[sp.length-1]:(d.generatedAt||"");
+ const days=asOf?Math.floor((Date.now()-Date.parse(asOf))/86400000):-1;
+ const gap=its.filter(i=>!i.image).length;
+ process.stdout.write(asOf+" "+days+" "+gap+" "+its.length);
+ }catch(e){ process.stdout.write("- -1 -1 0"); }' 2>/dev/null)"
+
+# ── 3. ComfyUI availability (for image-gap fills) ──
+COMFY=0
+for port in ${COMFY_PORTS:-8188 8189 8000}; do
+ if curl -s -o /dev/null --max-time 3 "http://127.0.0.1:$port/" 2>/dev/null; then COMFY=1; COMFY_PORT=$port; break; fi
+done
+
+DRAFTED=""
+
+# ── 4a. GATED: metered live-refresh request when stale beyond threshold (idempotent per data vintage) ──
+if [ "$STALE_DAYS" -ge "$STALE_THRESHOLD" ] 2>/dev/null; then
+ SLUG="trending-dw-refresh-${AS_OF}.md"
+ # skip if a memo for THIS data vintage already exists anywhere in the queue (pending or resolved)
+ if [ -z "$(find "$PENDING" -name "$SLUG" 2>/dev/null | head -1)" ]; then
+ cat > "$PENDING/$SLUG" <<EOF
+# GATED APPROVAL — trending-dw metered trend refresh
+
+**Drafted:** $NOW (autonomous 10-min tick)
+**Officer of record:** vp-dw-commerce
+**Why:** the trending board's data is **$STALE_DAYS days old** (as of $AS_OF), past the ${STALE_THRESHOLD}-day cadence. A *trending* board is only as credible as its freshness.
+
+## The ask
+Approve a metered **design-trend-scout (Exa)** refresh to re-pull current bestsellers, then re-attach our-own images and stage a deploy.
+
+- **Cost:** metered Exa research pull — estimate a few \$ per run (show + log actual to cost-tracker before running). This is the ONLY spend; the tick itself is \$0.
+- **Command (run after approval):** \`bash ~/Projects/trending-dw/scripts/refresh.sh\` once wired to live Exa (currently re-seeds committed data — the live wiring is the gated step).
+
+## APPROVE / REVISE / BLOCK
+- [ ] APPROVE — run the metered refresh + draft the deploy
+- [ ] REVISE — change cadence/threshold (currently ${STALE_THRESHOLD}d)
+- [ ] BLOCK — leave the board at its last approved vintage
+EOF
+ DRAFTED="${DRAFTED}refresh:$SLUG "
+ fi
+fi
+
+# ── 4b. OPPORTUNITY: ComfyUI came back → the 13 image gaps can be filled to staging (idempotent on transition) ──
+LAST_COMFY="$(cat "$STATE/comfy" 2>/dev/null || echo 0)"
+if [ "$COMFY" = "1" ] && [ "$LAST_COMFY" != "1" ] && [ "${IMG_GAP:-0}" -gt 0 ] 2>/dev/null; then
+ SLUG="trending-dw-imagegap-${DAY}.md"
+ if [ -z "$(find "$PENDING" -name "$SLUG" 2>/dev/null | head -1)" ]; then
+ cat > "$PENDING/$SLUG" <<EOF
+# OPPORTUNITY — trending-dw image-gap fill (ComfyUI is back)
+
+**Drafted:** $NOW (autonomous 10-min tick)
+**Officer of record:** vp-dw-commerce / wallpapersback-agent (settlement-gated generation)
+**Why:** ComfyUI is UP (port ${COMFY_PORT:-?}) and **$IMG_GAP of $COUNT** trending cards still lack an our-own image (the seam-gate wall that blocked TR generation). Now fillable to LOCAL staging.
+
+## The ask
+Generate the $IMG_GAP missing our-own designs to staging (settlement gate MANDATORY, is_published=FALSE), then attach + stage a deploy.
+
+- **Cost:** local SDXL/ComfyUI = \$0 (local GPU); any metered fallback shown separately.
+- **Guardrail:** generation runs through the settlement gate; nothing publishes without a separate go.
+
+## APPROVE / REVISE / BLOCK
+- [ ] APPROVE — generate the $IMG_GAP gaps to staging
+- [ ] REVISE — subset / different lanes
+- [ ] BLOCK — leave the gap cards on the 'pending' affordance
+EOF
+ DRAFTED="${DRAFTED}imagegap:$SLUG "
+ fi
+fi
+echo "$COMFY" > "$STATE/comfy"
+
+# ── 5. append the tick ledger ($0 local) ──
+printf '{"ts":"%s","live_http":"%s","up":%s,"as_of":"%s","stale_days":%s,"img_gap":%s,"count":%s,"comfy":%s,"drafted":"%s","cost":"$0 (local)"}\n' \
+ "$NOW" "$HTTP" "$UP" "$AS_OF" "${STALE_DAYS:-null}" "${IMG_GAP:-null}" "${COUNT:-0}" "$COMFY" "${DRAFTED:-none}" >> "$LEDGER"
+
+echo "tick $NOW · live=$HTTP · stale=${STALE_DAYS}d · img_gap=$IMG_GAP · comfy=$COMFY · drafted=[${DRAFTED:-none}] · cost=\$0 (local)"
← ab31ac0 trending: data-freshness badge + per-card spotted timestamp
·
back to Trending Dw
·
trending: fix contrarian-caught issues (onerror injection, h 955e9e4 →