[object Object]

← back to Wolfgordon Crawl

Wire net-new pusher to shared variant-budget ledger + kill-switch so it can't burst the daily cap; add description to the ACTIVE-gate (else DRAFT+Needs-Description)

2fe399eb1fccec40abec099702a9b43337bfee88 · 2026-06-19 17:08:13 -0700 · Steve

Files touched

Diff

commit 2fe399eb1fccec40abec099702a9b43337bfee88
Author: Steve <steve@designerwallcoverings.com>
Date:   Fri Jun 19 17:08:13 2026 -0700

    Wire net-new pusher to shared variant-budget ledger + kill-switch so it can't burst the daily cap; add description to the ACTIVE-gate (else DRAFT+Needs-Description)
---
 push-shopify.js | 69 +++++++++++++++++++++++++++++++++++++++++++++++++++++----
 1 file changed, 65 insertions(+), 4 deletions(-)

diff --git a/push-shopify.js b/push-shopify.js
index 9f1eb44..44d8d41 100644
--- a/push-shopify.js
+++ b/push-shopify.js
@@ -26,6 +26,24 @@
 // ============================================================================
 
 const { pool } = require('./scraper-utils');
+const os = require('os');
+const fs = require('fs');
+
+// ---------- shared cap coordination (DTD-C variant-budget ledger) ----------
+// The net-new create path used to bulk-dump products outside the paced cadence,
+// which is what blew the ~1,000/day Shopify variant cap on 2026-06-19 (10–11am).
+// It now draws from the SAME shared daily ledger every other DW writer uses
+// (budget.cjs, category 'upload' — the ACTIVE phase), so creates-per-run can
+// never exceed the day's remaining grant. Honors the shared kill-switch too.
+// Pattern mirrors shopify/scripts/cadence/run-cadence-hourly.sh.
+const KILL_SWITCH = os.homedir() + '/.dw-fixer-stop';
+let budget = null;
+try {
+  budget = require(os.homedir() + '/Projects/designerwallcoverings/scripts/variant-budget/budget.cjs');
+} catch (e) {
+  console.error('  [budget] WARN: ledger not loadable (' + e.message + ') — net-new creates will be BLOCKED (fail-CLOSED on the cap).');
+}
+const VARIANTS_PER_PRODUCT = 2; // every product ships Yard + Sample
 
 // ---------- config ----------
 const SHOP = 'designer-laboratory-sandbox.myshopify.com';
@@ -338,15 +356,21 @@ async function createProduct(row, { draft } = {}) {
   if (!title) { console.log(`    SKIP (no usable color) mfr=${row.mfr_sku}`); return null; }
   const imgs = imageList(row);
   const width = widthInches(row);
-  const isDraft = draft || imgs.length === 0 || !width;
+  const descriptionHtml = buildDescriptionHtml(row);
+  const fullMetafields = buildFullMetafields(row);
+
+  // Activation gate (Steve's hard rules): a product goes ACTIVE only with an
+  // image AND a width AND a non-trivial description. Missing any -> DRAFT + the
+  // matching Needs-* tag, never a bare live product. (feedback_all_new_products_
+  // must_have_description, 2026-06-19.)
+  const hasDescription = !!descriptionHtml;
+  const isDraft = draft || imgs.length === 0 || !width || !hasDescription;
   const tags = [VENDOR, row.collection].filter(Boolean);
   if (imgs.length === 0) tags.push('Needs-Image');
   if (!width) tags.push('Needs-Width');
+  if (!hasDescription) tags.push('Needs-Description');
   if (draft) tags.push('Quote-Only');
 
-  const descriptionHtml = buildDescriptionHtml(row);
-  const fullMetafields = buildFullMetafields(row);
-
   const productSet = {
     title,
     vendor: VENDOR,
@@ -528,6 +552,43 @@ async function main() {
     work = plan;
   }
 
+  // ── shared-cap gate on the NET-NEW create path ──────────────────────────────
+  // Updates/archives create NO new variants, so they are never cap-gated. Only
+  // the create path (work.new + work.draft, 2 variants each) draws the budget.
+  if (APPLY && (work.new.length || work.draft.length)) {
+    // 1. shared kill-switch — if present, drop all creates this run.
+    if (fs.existsSync(KILL_SWITCH)) {
+      console.log(`  KILL-SWITCH ${KILL_SWITCH} present — skipping ALL net-new creates this run.`);
+      work.new = []; work.draft = [];
+    } else {
+      // 2. draw from the shared daily ledger ('upload' = the ACTIVE-phase
+      //    category). The grant is in VARIANTS; each product = 2 variants.
+      //    Cap creates-per-run to floor(grant/2) so this trickles within the
+      //    18/hr paced budget instead of dumping hundreds.
+      const wantProducts = work.new.length + work.draft.length;
+      const wantVariants = wantProducts * VARIANTS_PER_PRODUCT;
+      let grantVariants;
+      if (!budget) {
+        // ledger unreachable -> fail-CLOSED on the create path (the cap is the
+        // whole point of this rewire; never burst it on a missing ledger).
+        grantVariants = 0;
+        console.log('  [budget] ledger unavailable — creating 0 net-new products this run (fail-closed on the cap).');
+      } else {
+        grantVariants = budget.take('upload', wantVariants);
+      }
+      const grantProducts = Math.floor(grantVariants / VARIANTS_PER_PRODUCT);
+      console.log(`  [budget] net-new requested ${wantProducts} products (${wantVariants} variants) -> granted ${grantVariants} variants = ${grantProducts} products this run.`);
+      // Spend the grant on NEW first, then DRAFT (quote-only).
+      const newKeep = Math.min(work.new.length, grantProducts);
+      const draftKeep = Math.min(work.draft.length, Math.max(0, grantProducts - newKeep));
+      if (newKeep < work.new.length || draftKeep < work.draft.length) {
+        console.log(`  [budget] capping creates: new ${work.new.length}->${newKeep}, draft ${work.draft.length}->${draftKeep} (deferred to a later paced run).`);
+      }
+      work.new = work.new.slice(0, newKeep);
+      work.draft = work.draft.slice(0, draftKeep);
+    }
+  }
+
   const total = work.new.length + work.update.length + work.draft.length + work.archive.length;
   console.log(`  ACTIONING: new=${work.new.length} update=${work.update.length} draft=${work.draft.length} archive=${work.archive.length} (total ${total})`);
   console.log('-'.repeat(74));

← 55477d3 Add targeted room-image re-scrape ($0 HTTP, resumable): re-f  ·  back to Wolfgordon Crawl  ·  push: publish ACTIVE products to ALL channels + set inventor f412021 →