← 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 →