← back to Designer Wallcoverings
Generalize Bucket-B lander: bucketB-land-all.sh drains ALL staged roll cohorts under one DW_ROLL_CAP (default 400)
e8b5ae152e84b0599e5acf4fcfeb1ed39d104206 · 2026-06-22 15:21:06 -0700 · Steve
Supersedes bucketB-land-175.sh (Harlequin+MTG only). Auto-discovers every
bucketB-*-roll-variants.js, runs each --apply --report under one DW_ROLL_CAP,
robust multi-field staged-count extractor, self-unloads only when all cohorts
report 0 staged. DW_ROLL_CAP=400 default (ceiling-clamped, can never breach 1000).
launchd com.steve.dw-bucketB-land repointed at it (still 00:01).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Files touched
A shopify/scripts/bucketB-land-all.sh
Diff
commit e8b5ae152e84b0599e5acf4fcfeb1ed39d104206
Author: Steve <steve@designerwallcoverings.com>
Date: Mon Jun 22 15:21:06 2026 -0700
Generalize Bucket-B lander: bucketB-land-all.sh drains ALL staged roll cohorts under one DW_ROLL_CAP (default 400)
Supersedes bucketB-land-175.sh (Harlequin+MTG only). Auto-discovers every
bucketB-*-roll-variants.js, runs each --apply --report under one DW_ROLL_CAP,
robust multi-field staged-count extractor, self-unloads only when all cohorts
report 0 staged. DW_ROLL_CAP=400 default (ceiling-clamped, can never breach 1000).
launchd com.steve.dw-bucketB-land repointed at it (still 00:01).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
---
shopify/scripts/bucketB-land-all.sh | 120 ++++++++++++++++++++++++++++++++++++
1 file changed, 120 insertions(+)
diff --git a/shopify/scripts/bucketB-land-all.sh b/shopify/scripts/bucketB-land-all.sh
new file mode 100755
index 00000000..7071d1f7
--- /dev/null
+++ b/shopify/scripts/bucketB-land-all.sh
@@ -0,0 +1,120 @@
+#!/bin/bash
+# bucketB-land-all.sh (2026-06-22) — owner: vp-dw-commerce
+# Supersedes bucketB-land-175.sh (Harlequin+MTG only). Generalized lander that drains
+# EVERY staged Bucket-B priced-roll cohort in one launchd job, under one DW_ROLL_CAP.
+#
+# ─────────────────────────────────────────────────────────────────────────────────────
+# DW_ROLL_CAP — the ONE editable knob (cadence vs backlog tradeoff is Steve's to tune).
+# Default 400: drains the ~854-row priced-roll backlog over ~2–3 nights while leaving the
+# live cadence/upload pipeline meaningful headroom (≥600 of the 1000 ceiling). To blast the
+# whole backlog in a single night, set DW_ROLL_CAP=900 below (still ceiling-safe, see MATH).
+# ─────────────────────────────────────────────────────────────────────────────────────
+export DW_ROLL_CAP="${DW_ROLL_CAP:-400}"
+#
+# MATH — why DW_ROLL_CAP can NEVER breach the shared 1000/day ceiling (verified against
+# budget.cjs take()): grant = min(n, remainingWho, remainingGlobal) where
+# remainingWho = DW_ROLL_CAP - spent.roll (roll never grants > DW_ROLL_CAP total/day)
+# remainingGlobal = BUDGET_TOTAL - spentTotal (roll + every other category never > 1000)
+# So DW_ROLL_CAP is a PRIORITY knob, not a ceiling override — at 00:01 (before osborne 00:05 /
+# cadence 00:40) roll reserves up to DW_ROLL_CAP of the fresh 1000 FIRST; cadence/backlog/upload
+# compete for whatever's left (≥ 1000 − DW_ROLL_CAP). DW_ROLL_CAP=900 → roll reserves up to 900,
+# others get ≥100; DW_ROLL_CAP=1000+ would still be clamped to remainingGlobal → can't exceed 1000.
+#
+# Each bucketB-*-roll-variants.js is: idempotent (skips products that already have a roll
+# variant), draft-gated (writes PG retail_price first as source of truth, never flips status,
+# never touches title/customer-facing vendor/display_variant), and budget-aware (take('roll')
+# up front, refund the unused grant). Re-running on a later night drains any remainder.
+#
+# SELF-UNLOAD: this job boots itself out ONLY when every discovered script reports 0 staged
+# priced rows this run. If ANY script leaves a remainder (budget couldn't fit all under the
+# cap), the job stays loaded and retries next night at 00:01.
+
+set -uo pipefail
+export DW_ROLL_CAP # already defaulted above; ensure it's exported to the node children
+
+SCRIPTS_DIR="$HOME/Projects/Designer-Wallcoverings/shopify/scripts"
+REPORT_DIR="$SCRIPTS_DIR/data/bucketB"
+LOG_DIR="$HOME/Projects/Designer-Wallcoverings/logs"
+BUDGET="$HOME/Projects/designerwallcoverings/scripts/variant-budget/budget.cjs"
+TS="$(date +%Y%m%dT%H%M%S)"
+mkdir -p "$REPORT_DIR" "$LOG_DIR"
+LOG="$LOG_DIR/bucketB-land-all-$TS.log"
+
+# launchd has a minimal PATH — pin homebrew/node first.
+export PATH="/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin:$PATH"
+NODE="$(command -v node || echo /opt/homebrew/bin/node)"
+
+# Robust staged-count extractor: the bucketB scripts use DIFFERENT field names for "priced rows
+# left unstaged this run" — harlequin/grahambrown=totals.stagedForNextWindow,
+# mindthegap/nina/scalamandre=totals.pricedBacklogStaged, coordonne=totals.deferredByCap,
+# schumacher=totals.budgetDeferredPriced. Read whichever exists; missing/parse-fail → "1"
+# (treat as "remainder unknown" so we DON'T self-unload on a bad/absent report — fail safe).
+staged_count() {
+ "$NODE" -e '
+ try {
+ const r = require(process.argv[1]);
+ const t = (r && r.totals) || {};
+ const v = [t.stagedForNextWindow, t.pricedBacklogStaged, t.deferredByCap, t.budgetDeferredPriced]
+ .find(x => typeof x === "number");
+ process.stdout.write(String(typeof v === "number" ? v : 1));
+ } catch (e) { process.stdout.write("1"); }
+ ' "$1" 2>/dev/null || printf '1'
+}
+
+{
+ echo "=== bucketB-land-all $TS (DW_ROLL_CAP=$DW_ROLL_CAP) ==="
+ echo "--- pre-run budget status ---"
+ "$NODE" "$BUDGET" status 2>&1 || echo "(budget status unavailable)"
+
+ # Auto-discover EVERY bucketB roll-variant script (future vendors picked up automatically).
+ # Sorted for deterministic order; rebelwalls is already landed but is idempotent so harmless.
+ shopt -s nullglob
+ SCRIPTS=( "$SCRIPTS_DIR"/bucketB-*-roll-variants.js )
+ shopt -u nullglob
+
+ TOTAL_STAGED=0
+ ANY_UNKNOWN=0
+ echo ""
+ echo "--- will run ${#SCRIPTS[@]} bucketB roll scripts ---"
+ for s in "${SCRIPTS[@]}"; do echo " $(basename "$s")"; done
+
+ for SCRIPT in "${SCRIPTS[@]}"; do
+ NAME="$(basename "$SCRIPT" -roll-variants.js)" # e.g. bucketB-nina
+ SHORT="${NAME#bucketB-}" # e.g. nina
+ RPT="$REPORT_DIR/${SHORT}-land-$TS.json"
+ echo ""
+ echo "=== $SHORT ==="
+ "$NODE" "$SCRIPT" --apply --report "$RPT"
+ RC=$?
+ ST="$(staged_count "$RPT")"
+ echo " → rc=$RC staged_remaining=$ST"
+ # Tally. A non-numeric/"1"-from-missing-report counts as a remainder (keeps job loaded).
+ if [[ "$ST" =~ ^[0-9]+$ ]]; then
+ TOTAL_STAGED=$(( TOTAL_STAGED + ST ))
+ else
+ ANY_UNKNOWN=1
+ fi
+ done
+
+ echo ""
+ echo "--- post-run budget status ---"
+ "$NODE" "$BUDGET" status 2>&1 || echo "(budget status unavailable)"
+ echo ""
+ echo "TOTAL staged remaining across all scripts: $TOTAL_STAGED (any_unknown=$ANY_UNKNOWN)"
+
+ # Self-unload ONLY when fully drained AND no script had an unknown/missing report.
+ if [ "$TOTAL_STAGED" -eq 0 ] && [ "$ANY_UNKNOWN" -eq 0 ]; then
+ echo "✅ all Bucket-B cohorts drained — unloading one-shot job com.steve.dw-bucketB-land"
+ launchctl bootout "gui/$(id -u)/com.steve.dw-bucketB-land" 2>/dev/null || \
+ launchctl unload "$HOME/Library/LaunchAgents/com.steve.dw-bucketB-land.plist" 2>/dev/null
+ else
+ echo "↻ remainder staged ($TOTAL_STAGED) — leaving job loaded to retry next night at 00:01"
+ fi
+
+ # CNCP win (local, best-effort).
+ curl -s -X POST http://127.0.0.1:3333/api/wins \
+ -H 'Content-Type: application/json' \
+ -d "{\"project\":\"dw-bucketB\",\"title\":\"Bucket-B roll lander (all vendors)\",\"summary\":\"DW_ROLL_CAP=$DW_ROLL_CAP drained ${#SCRIPTS[@]} cohorts; total_staged_remaining=$TOTAL_STAGED\"}" >/dev/null 2>&1 || true
+
+ echo "=== done $(date +%Y%m%dT%H%M%S) ==="
+} >> "$LOG" 2>&1
← e2ae1b4d Add native-Liquid infinite scroll for theme 142250278963 (co
·
back to Designer Wallcoverings
·
auto-save: 2026-06-22T15:37:11 (67 files) — shopify/scripts/ 2dc3a7fe →