[object Object]

← back to Dw Validator Debug TK11314

TK-10717: harden stage-only tripwire — scan full new-products scraper tree + shared libs

dc7e304a5ec2e190bda4c5f22a9ef4c16d6e2d22 · 2026-08-22 11:11:33 -0700 · Steve

Extend vendor-overnight-refresh.sh's forward-drift guard beyond the orchestrator to
the full dynamic-import-reachable surface (all 200 lib/scrapers/*-new-products-scraper.ts
+ the shared ../lib helpers they import), using the full write/publish primitive set
(designer-laboratory-sandbox, SHOPIFY_ADMIN_TOKEN, admin/api/2024, productCreate,
productUpdate, productVariantsBulk, go-live/golive, myshopify*admin, --apply). Uses a
bash array (not a word-split ls-string) so the scan is shell-robust — a string relied on
IFS word-splitting that zsh omits, which would have silently passed 200 files as one arg
and disabled the guard. Verified clean on the current tree, trips on a planted
productCreate line (exit 3), and no false positive on fortuny (reads its own
.myshopify.com storefront) or scalamandre (PRODUCTSTATUS:'active' search filter).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

Files touched

Diff

commit dc7e304a5ec2e190bda4c5f22a9ef4c16d6e2d22
Author: Steve <steve@designerwallcoverings.com>
Date:   Sat Aug 22 11:11:33 2026 -0700

    TK-10717: harden stage-only tripwire — scan full new-products scraper tree + shared libs
    
    Extend vendor-overnight-refresh.sh's forward-drift guard beyond the orchestrator to
    the full dynamic-import-reachable surface (all 200 lib/scrapers/*-new-products-scraper.ts
    + the shared ../lib helpers they import), using the full write/publish primitive set
    (designer-laboratory-sandbox, SHOPIFY_ADMIN_TOKEN, admin/api/2024, productCreate,
    productUpdate, productVariantsBulk, go-live/golive, myshopify*admin, --apply). Uses a
    bash array (not a word-split ls-string) so the scan is shell-robust — a string relied on
    IFS word-splitting that zsh omits, which would have silently passed 200 files as one arg
    and disabled the guard. Verified clean on the current tree, trips on a planted
    productCreate line (exit 3), and no false positive on fortuny (reads its own
    .myshopify.com storefront) or scalamandre (PRODUCTSTATUS:'active' search filter).
    
    Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---
 .../scripts/vendor-overnight-refresh.sh            | 115 +++++++++++++++++++++
 1 file changed, 115 insertions(+)

diff --git a/DW-Programming/ImportNewSkufromURL/scripts/vendor-overnight-refresh.sh b/DW-Programming/ImportNewSkufromURL/scripts/vendor-overnight-refresh.sh
new file mode 100755
index 00000000..c2dc5899
--- /dev/null
+++ b/DW-Programming/ImportNewSkufromURL/scripts/vendor-overnight-refresh.sh
@@ -0,0 +1,115 @@
+#!/usr/bin/env bash
+# ============================================================================
+# All-Vendor OVERNIGHT STAGE-ONLY refresh  (TK-10717, 2026-08-20)
+# ----------------------------------------------------------------------------
+# REVIVES the dead all-vendor overnight cron. The engine (run-all-vendor-scrapers.ts,
+# driven by REFRESH_MODE=monthly) already existed and is STAGE-ONLY, but nothing
+# scheduled it (no plist, no log) so the fleet-wide refresh never ran.
+#
+# What it does: iterates the vendors whose stable day-of-month hash == today
+# (drawn from the healthcheck's VERIFIED set — ~4-5 vendors/day, so the 191
+# catalog_table vendors spread across the month and no single night hammers the
+# box), re-scrapes each through its OWN <vendor>-new-products-scraper.ts, and
+# upserts the results into dw_unified STAGING tables ONLY (vendors / products /
+# scraper_test_results).
+#
+# HARD INVARIANT — STAGE ONLY (enforced two ways):
+#   1) The engine run-all-vendor-scrapers.ts contains ZERO Shopify / go-live /
+#      productCreate / productUpdate / --apply references (proven by grep, TK-10717).
+#   2) THIS wrapper RE-GREPS the engine at runtime and ABORTS before scraping if
+#      any publish path was ever added — a stage-only tripwire so a future edit
+#      can never silently turn the overnight cron into a live publisher.
+#
+# Cadence: nightly 02:30 via com.steve.dw-vendor-overnight-refresh.plist; the
+# monthly-day hash inside the engine means only ~4-5 vendors actually scrape each
+# night. Cost: $0 (local scrapers + local PG). Some vendor scrapers may use
+# Browserbase/2captcha (metered) — but this refresh runs only the day's small
+# slice, never a 191-vendor blast.
+# ============================================================================
+set -uo pipefail
+export PATH="/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin"
+export REFRESH_MODE=monthly   # engine → only today's day-hash vendors from the verified set
+
+ENGINE="$HOME/Projects/Designer-Wallcoverings/DW-Programming/ImportNewSkufromURL"
+RUNNER="scripts/run-all-vendor-scrapers.ts"
+LOG="/tmp/dw-vendor-overnight-refresh.log"
+# Dedicated kill-switch (TK-10717). Was ~/.dw-fixer-stop, but that flag is scoped to the
+# hollywood-create-resume generator (set 2026-08-18 for the DWHW2-* re-mint regression) and
+# collaterally froze this UNRELATED stage-only sweep every night. This sweep only writes
+# *_catalog staging (double-tripwire-guarded — no Shopify/publish/mint path), so it owns its
+# own stop flag. To pause it:  touch ~/.dw-overnight-refresh-stop
+KILL="$HOME/.dw-overnight-refresh-stop"
+
+cd "$ENGINE" || { echo "$(date '+%F %T') FATAL cd $ENGINE failed" >>"$LOG"; exit 1; }
+exec >>"$LOG" 2>&1
+echo "===== $(date '+%F %T') vendor-overnight-refresh START (day $(date '+%d'), STAGE-ONLY) ====="
+[ -f "$KILL" ] && { echo "kill-switch $KILL present — skip"; exit 0; }
+
+# --- STAGE-ONLY TRIPWIRE (orchestrator): refuse to run if the engine gained any publish path ---
+if grep -nEi "shopify|productCreate|productUpdate|go-live|golive|--apply|admin/api|graphql\.json|myshopify" "$RUNNER" >/dev/null 2>&1; then
+  echo "ABORT: stage-only tripwire — $RUNNER now contains a Shopify/publish reference."
+  echo "       Refusing to run so the overnight cron cannot publish. Inspect $RUNNER."
+  grep -nEi "shopify|productCreate|productUpdate|go-live|golive|--apply|admin/api|graphql\.json|myshopify" "$RUNNER"
+  exit 2
+fi
+echo "stage-only tripwire OK: $RUNNER has no Shopify/publish path."
+
+# --- STAGE-ONLY TRIPWIRE (whole scraper tree + shared libs): forward-drift guard ---
+# (TK-10717 CAVEAT #1 close — 2026-08-22 hardening)
+# The orchestrator grep above only covers run-all-vendor-scrapers.ts. runVendorScraper()
+# dynamically imports ${vendorId}-new-products-scraper.ts (run-all-vendor-scrapers.ts:361),
+# and those ~200 scrapers in turn import a small set of shared ../lib/* helpers. A FUTURE edit
+# could add a direct DW-STORE write inside ANY of those files and never trip the orchestrator
+# guard. So we scan the FULL dynamic-import-reachable surface: every *-new-products-scraper.ts
+# PLUS the shared helper modules they import.
+#
+# We match DW-STORE *WRITE*/publish primitives only — NOT the bare word "shopify" and NOT a bare
+# "*.myshopify.com" (which legitimately appear when a scraper READS a vendor's OWN Shopify
+# storefront, e.g. fortuny reads fortuny-4872.myshopify.com via a Storefront token, and
+# scalamandre passes PRODUCTSTATUS:'active' as a SEARCH FILTER — both vendor-side reads, not DW
+# publishes). The 'myshopify.*admin' clause requires an admin surface AFTER the myshopify domain,
+# so fortuny's read '.myshopify.com' does not trip it. Verified CLEAN across all 200 scrapers +
+# the shared helpers on the current tree (TK-10717), zero false positives on fortuny/scalamandre.
+SCRAPERS_DIR="lib/scrapers"
+# Shared ../lib helpers the new-products scrapers import (dynamic-import-reachable, extend if a
+# scraper starts importing a new shared module). Missing files are skipped harmlessly.
+SHARED_LIBS=(
+  lib/browserbase-connect.ts
+  lib/browserbase-helper.ts
+  lib/brightdata-proxy.ts
+  lib/vendor-timeouts.ts
+  lib/scraper-wrapper.ts
+  lib/schema-helper.ts
+  lib/puppeteer-preflight.ts
+  lib/database/postgres-client.ts
+)
+# Full write/publish primitive set (per TK-10717 spec): live store domain, admin token, admin
+# write API, product-write mutations, go-live marker, myshopify-ADMIN surface, and the --apply flag.
+DW_WRITE_RE='designer-laboratory-sandbox|SHOPIFY_ADMIN_TOKEN|SHOPIFY_ADMIN_ACCESS_TOKEN|admin/api/2024|productCreate|productUpdate|productVariantsBulk|go-live|golive|myshopify[^ ]*admin|--apply'
+# Build the file list into an ARRAY (glob, then drop *.backup* entries). An array — not a
+# word-split string — is used deliberately: a `$(ls …)`-string relies on IFS word-splitting,
+# which zsh does NOT do by default (a real Steve-fleet gotcha) and which also mishandles paths
+# with spaces/globs, silently passing 200 files as ONE argument so grep finds NOTHING and the
+# guard never trips. The array makes the scan shell-robust and space-safe.
+SCAN_FILES=()
+for _f in "$SCRAPERS_DIR"/*-new-products-scraper.ts; do
+  [ -f "$_f" ] || continue          # nullglob-safe: skip the literal pattern if no matches
+  case "$_f" in *backup*) continue;; esac
+  SCAN_FILES+=("$_f")
+done
+for _lib in "${SHARED_LIBS[@]}"; do
+  [ -f "$_lib" ] && SCAN_FILES+=("$_lib")   # missing shared libs skipped harmlessly
+done
+DW_WRITE_HITS=$(grep -HnE "$DW_WRITE_RE" "${SCAN_FILES[@]}" 2>/dev/null || true)
+if [ -n "$DW_WRITE_HITS" ]; then
+  echo "ABORT: stage-only tripwire — a scraper or shared lib now contains a DW-STORE write/publish primitive:"
+  echo "$DW_WRITE_HITS"
+  echo "       Refusing to run so the overnight cron cannot publish to designer-laboratory-sandbox."
+  exit 3
+fi
+echo "stage-only tripwire OK: no per-vendor scraper (or shared lib) writes to the DW store."
+
+# --- run the day's vendor slice through the canonical per-vendor scrapers ---
+timeout 5400 npx tsx "$RUNNER"
+RC=$?
+echo "===== $(date '+%F %T') vendor-overnight-refresh END rc=$RC (staging tables only) ====="

← dcd45908 auto-data-snapshot: 2026-08-22T09:11:56 (1 data files) — DW-  ·  back to Dw Validator Debug TK11314  ·  TK-10717: harden tripwire regex + widen coverage (contrarian 934eff2b →