[object Object]

← 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

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 →