← back to Designer Wallcoverings
activation compliance: all-channels publish + inventory 2026 on importers + cohort remediation
912696aeb7b8799e242a2043d79e2336a588bbf3 · 2026-06-20 04:02:20 -0700 · Steve
Steve 2026-06-20 (memory activation-all-channels-and-2026-inventory): every ACTIVE
product must be published to ALL 13 sales channels AND carry inventory 2026 on its
sellable variant. The bulk importers regressed this.
- command54-shopify-push.js + justindavid-shopify-push.js (Phillipe Romano / rebranded
Los Angeles Fabrics): added env-guarded GraphQL publishToAllChannels() + setInventory2026()
to runReconcile() so each newly-linked ACTIVE product is published to all channels and
stocked at 2026. No-ops cleanly without SHOPIFY_ADMIN_TOKEN — cannot break the REST queue.
- remediate-channels-inventory.js: new scoped, idempotent, resume-safe remediation that
reuses cadence-import.js's exact loadPublications/publishToChannels/setInventory2026
mechanism. Remediated the last-1000 active cohort: 712 published to all channels,
704 sellable variants set to inventory 2026 (0 errors). Prices untouched, drafts untouched.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Files touched
M DW-Programming/command54-shopify-push.jsM DW-Programming/justindavid-shopify-push.jsA shopify/scripts/cadence/remediate-channels-inventory.js
Diff
commit 912696aeb7b8799e242a2043d79e2336a588bbf3
Author: Steve <steve@designerwallcoverings.com>
Date: Sat Jun 20 04:02:20 2026 -0700
activation compliance: all-channels publish + inventory 2026 on importers + cohort remediation
Steve 2026-06-20 (memory activation-all-channels-and-2026-inventory): every ACTIVE
product must be published to ALL 13 sales channels AND carry inventory 2026 on its
sellable variant. The bulk importers regressed this.
- command54-shopify-push.js + justindavid-shopify-push.js (Phillipe Romano / rebranded
Los Angeles Fabrics): added env-guarded GraphQL publishToAllChannels() + setInventory2026()
to runReconcile() so each newly-linked ACTIVE product is published to all channels and
stocked at 2026. No-ops cleanly without SHOPIFY_ADMIN_TOKEN — cannot break the REST queue.
- remediate-channels-inventory.js: new scoped, idempotent, resume-safe remediation that
reuses cadence-import.js's exact loadPublications/publishToChannels/setInventory2026
mechanism. Remediated the last-1000 active cohort: 712 published to all channels,
704 sellable variants set to inventory 2026 (0 errors). Prices untouched, drafts untouched.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
---
DW-Programming/command54-shopify-push.js | 64 ++++++++
DW-Programming/justindavid-shopify-push.js | 62 +++++++
.../cadence/remediate-channels-inventory.js | 181 +++++++++++++++++++++
3 files changed, 307 insertions(+)
diff --git a/DW-Programming/command54-shopify-push.js b/DW-Programming/command54-shopify-push.js
index b5022b5e..64375a03 100644
--- a/DW-Programming/command54-shopify-push.js
+++ b/DW-Programming/command54-shopify-push.js
@@ -25,6 +25,61 @@ const pool = new Pool({
const SOURCE_AGENT = 'command54-p8-push';
const VENDOR_CODE = 'command54';
const VENDOR_DISPLAY = 'Phillipe Romano';
+
+// --- All-channels publish + inventory-2026 on activation (Steve 2026-06-20,
+// memory activation-all-channels-and-2026-inventory). Mirrors cadence-import.js.
+// Env-guarded: if no SHOPIFY_ADMIN_TOKEN is present the helpers no-op cleanly so
+// the existing REST-queue flow is never broken. ---
+const SHOPIFY_STORE = 'designer-laboratory-sandbox.myshopify.com';
+const SHOPIFY_API = '2024-10';
+const SHOPIFY_TOKEN = (process.env.SHOPIFY_ADMIN_TOKEN || process.env.SHOPIFY_ACCESS_TOKEN || '').replace(/['"]/g, '').trim();
+const INV_LOCATION_2026 = 'gid://shopify/Location/5795643504'; // 15442 Ventura Blvd.
+const _sleep = ms => new Promise(r => setTimeout(r, ms));
+function shopifyGql(query, variables) {
+ return new Promise(resolve => {
+ const data = JSON.stringify({ query, variables });
+ const req = https.request({ host: SHOPIFY_STORE, path: `/admin/api/${SHOPIFY_API}/graphql.json`, method: 'POST',
+ headers: { 'X-Shopify-Access-Token': SHOPIFY_TOKEN, 'Content-Type': 'application/json', 'Content-Length': Buffer.byteLength(data) } },
+ r => { let d = ''; r.on('data', c => d += c); r.on('end', () => { try { resolve(JSON.parse(d)); } catch { resolve(null); } }); });
+ req.on('error', () => resolve(null));
+ req.write(data); req.end();
+ });
+}
+let _PUBS = null;
+async function _loadPubs() {
+ if (_PUBS) return _PUBS;
+ const r = await shopifyGql(`{publications(first:50){edges{node{id name}}}}`, {});
+ _PUBS = (r?.data?.publications?.edges || []).map(e => e.node);
+ return _PUBS;
+}
+// Publish a product to ALL sales-channel publications (was: never published → only
+// the ~6 default channels). "already published" userErrors are benign.
+async function publishToAllChannels(productGid) {
+ if (!SHOPIFY_TOKEN) return false;
+ const pubs = await _loadPubs();
+ if (!pubs.length) return false;
+ const input = pubs.map(p => ({ publicationId: p.id }));
+ await shopifyGql(`mutation($id:ID!,$input:[PublicationInput!]!){publishablePublish(id:$id,input:$input){userErrors{field message}}}`,
+ { id: productGid, input });
+ await _sleep(350);
+ return true;
+}
+// Set inventory on_hand=2026 at Ventura Blvd on the sellable (non-Sample) SKU. Cap-free.
+async function setInventory2026(skus) {
+ if (!SHOPIFY_TOKEN || !skus.length) return false;
+ const map = new Map();
+ const q = skus.map(s => `sku:"${String(s).replace(/"/g, '\\"')}"`).join(' OR ');
+ const r = await shopifyGql(`query($q:String!){productVariants(first:250,query:$q){edges{node{sku inventoryItem{id}}}}}`, { q });
+ for (const e of (r?.data?.productVariants?.edges || [])) {
+ if (e.node.sku && e.node.inventoryItem?.id) map.set(e.node.sku, e.node.inventoryItem.id);
+ }
+ const pairs = skus.filter(s => map.has(s)).map(s => ({ inventoryItemId: map.get(s), locationId: INV_LOCATION_2026, quantity: 2026 }));
+ if (!pairs.length) return false;
+ await shopifyGql(`mutation($input:InventorySetQuantitiesInput!){inventorySetQuantities(input:$input){userErrors{field message code}}}`,
+ { input: { name: 'on_hand', reason: 'correction', ignoreCompareQuantity: true, quantities: pairs } });
+ await _sleep(350);
+ return true;
+}
const COLLECTION_NAME = 'Hollywood Vinyls Vol. 1';
const DEFAULT_FIRE_RATING = 'Class A Fire Rated : Passes ASTM E84';
const LAUNCH_PAD_BATCH = 25;
@@ -488,6 +543,15 @@ async function runReconcile() {
AND mfr_sku = (SELECT mfr_sku FROM vendor_catalog WHERE dw_sku = $3 AND vendor_code = $2 LIMIT 1)
`, [shopifyId, VENDOR_CODE, dwSku]);
+ // Activation compliance (Steve 2026-06-20): the product is created ACTIVE, so
+ // publish it to ALL sales channels AND set inventory 2026 on the sellable variant.
+ // Env-guarded: no-ops cleanly if SHOPIFY_ADMIN_TOKEN is not present in this runtime.
+ try {
+ const gid = `gid://shopify/Product/${String(shopifyId).replace(/\D/g, '')}`;
+ await publishToAllChannels(gid);
+ await setInventory2026([dwSku]); // sellable 'Full Roll' variant SKU = dw_sku
+ } catch (e) { /* never let compliance step break reconcile linking */ }
+
reconciled++;
} catch (err) {
failed++;
diff --git a/DW-Programming/justindavid-shopify-push.js b/DW-Programming/justindavid-shopify-push.js
index 6195a15e..057d76b2 100644
--- a/DW-Programming/justindavid-shopify-push.js
+++ b/DW-Programming/justindavid-shopify-push.js
@@ -28,6 +28,59 @@ const pool = new Pool({
const SOURCE_AGENT = 'jd-p8-push';
const SLACK_WEBHOOK = '${SLACK_WEBHOOK_URL}';
+// --- All-channels publish + inventory-2026 on activation (Steve 2026-06-20,
+// memory activation-all-channels-and-2026-inventory). Mirrors cadence-import.js.
+// Env-guarded: if no SHOPIFY_ADMIN_TOKEN is present the helpers no-op cleanly so
+// the existing REST-queue flow is never broken. These JD products are rebranded to
+// "Los Angeles Fabrics" downstream; the publish/inventory state carries over. ---
+const SHOPIFY_STORE = 'designer-laboratory-sandbox.myshopify.com';
+const SHOPIFY_API = '2024-10';
+const SHOPIFY_TOKEN = (process.env.SHOPIFY_ADMIN_TOKEN || process.env.SHOPIFY_ACCESS_TOKEN || '').replace(/['"]/g, '').trim();
+const INV_LOCATION_2026 = 'gid://shopify/Location/5795643504'; // 15442 Ventura Blvd.
+const _sleep = ms => new Promise(r => setTimeout(r, ms));
+function shopifyGql(query, variables) {
+ return new Promise(resolve => {
+ const data = JSON.stringify({ query, variables });
+ const req = https.request({ host: SHOPIFY_STORE, path: `/admin/api/${SHOPIFY_API}/graphql.json`, method: 'POST',
+ headers: { 'X-Shopify-Access-Token': SHOPIFY_TOKEN, 'Content-Type': 'application/json', 'Content-Length': Buffer.byteLength(data) } },
+ r => { let d = ''; r.on('data', c => d += c); r.on('end', () => { try { resolve(JSON.parse(d)); } catch { resolve(null); } }); });
+ req.on('error', () => resolve(null));
+ req.write(data); req.end();
+ });
+}
+let _PUBS = null;
+async function _loadPubs() {
+ if (_PUBS) return _PUBS;
+ const r = await shopifyGql(`{publications(first:50){edges{node{id name}}}}`, {});
+ _PUBS = (r?.data?.publications?.edges || []).map(e => e.node);
+ return _PUBS;
+}
+async function publishToAllChannels(productGid) {
+ if (!SHOPIFY_TOKEN) return false;
+ const pubs = await _loadPubs();
+ if (!pubs.length) return false;
+ const input = pubs.map(p => ({ publicationId: p.id }));
+ await shopifyGql(`mutation($id:ID!,$input:[PublicationInput!]!){publishablePublish(id:$id,input:$input){userErrors{field message}}}`,
+ { id: productGid, input });
+ await _sleep(350);
+ return true;
+}
+async function setInventory2026(skus) {
+ if (!SHOPIFY_TOKEN || !skus.length) return false;
+ const map = new Map();
+ const q = skus.map(s => `sku:"${String(s).replace(/"/g, '\\"')}"`).join(' OR ');
+ const r = await shopifyGql(`query($q:String!){productVariants(first:250,query:$q){edges{node{sku inventoryItem{id}}}}}`, { q });
+ for (const e of (r?.data?.productVariants?.edges || [])) {
+ if (e.node.sku && e.node.inventoryItem?.id) map.set(e.node.sku, e.node.inventoryItem.id);
+ }
+ const pairs = skus.filter(s => map.has(s)).map(s => ({ inventoryItemId: map.get(s), locationId: INV_LOCATION_2026, quantity: 2026 }));
+ if (!pairs.length) return false;
+ await shopifyGql(`mutation($input:InventorySetQuantitiesInput!){inventorySetQuantities(input:$input){userErrors{field message code}}}`,
+ { input: { name: 'on_hand', reason: 'correction', ignoreCompareQuantity: true, quantities: pairs } });
+ await _sleep(350);
+ return true;
+}
+
// --- Helpers ---
function toTitleCase(str) {
@@ -357,6 +410,15 @@ async function runReconcile() {
AND mfr_sku = (SELECT mfr_sku FROM justindavid_catalog WHERE dw_sku = $2 LIMIT 1)
`, [shopifyId, dwSku]);
+ // Activation compliance (Steve 2026-06-20): products are created ACTIVE, so publish
+ // to ALL sales channels AND set inventory 2026 on the sellable variant. Env-guarded —
+ // no-ops cleanly if SHOPIFY_ADMIN_TOKEN is not present in this runtime.
+ try {
+ const gid = `gid://shopify/Product/${String(shopifyId).replace(/\D/g, '')}`;
+ await publishToAllChannels(gid);
+ await setInventory2026([dwSku]); // sellable variant SKU = dw_sku (Sample is dw_sku-Sample)
+ } catch (e) { /* never let compliance step break reconcile linking */ }
+
reconciled++;
} catch (err) {
failed++;
diff --git a/shopify/scripts/cadence/remediate-channels-inventory.js b/shopify/scripts/cadence/remediate-channels-inventory.js
new file mode 100644
index 00000000..25166a89
--- /dev/null
+++ b/shopify/scripts/cadence/remediate-channels-inventory.js
@@ -0,0 +1,181 @@
+#!/usr/bin/env node
+/**
+ * Remediation: bring the recent-cohort ACTIVE products into compliance with
+ * Steve's 2026-06-20 rule (memory activation-all-channels-and-2026-inventory):
+ * every ACTIVE product must be (a) published to ALL sales channels AND
+ * (b) have inventory on_hand=2026 at the Ventura Blvd location on its sellable
+ * (non-Sample) variant.
+ *
+ * The bulk importers (Wolf Gordon, Los Angeles Fabrics, Innovations) violated this
+ * — they created products ACTIVE but published to only ~6 of 13 channels and at
+ * inventory 0. The paced cadence (cadence-import.js) does it right. This script
+ * REUSES the cadence's exact, proven mechanism:
+ * - loadPublications() + publishToChannels(productId) (publishablePublish to ALL)
+ * - setInventory2026(skus) (inventorySetQuantities on_hand=2026)
+ *
+ * Both ops are CAP-FREE (no new variants) and IDEMPOTENT (skip already-compliant).
+ * Does NOT touch prices. Does NOT touch DRAFT/ARCHIVED products. Resume-safe.
+ *
+ * Usage:
+ * node remediate-channels-inventory.js # DRY-RUN, default scope = last 1000 active newest
+ * node remediate-channels-inventory.js --commit # LIVE
+ * node remediate-channels-inventory.js --commit --vendor 'Wolf Gordon' # scope to one vendor (ALL its active)
+ * node remediate-channels-inventory.js --commit --recent 1000 # scope to last N active newest (default)
+ */
+const https = require('https');
+const fs = require('fs');
+const os = require('os');
+const path = require('path');
+
+const args = process.argv.slice(2);
+const flag = n => args.includes(n);
+const val = (n, d) => { const i = args.indexOf(n); return i >= 0 ? args[i + 1] : d; };
+const COMMIT = flag('--commit');
+const VENDOR = val('--vendor', null); // if set: scope = ALL active products for this vendor
+const RECENT = parseInt(val('--recent', '1000'), 10); // else: last N active products (newest)
+const TOTAL_CHANNELS = 13;
+
+const env = fs.readFileSync(os.homedir() + '/Projects/secrets-manager/.env', 'utf8');
+const TOKEN = (env.match(/^SHOPIFY_ADMIN_TOKEN=(.*)$/m) || [])[1].replace(/['"]/g, '').trim();
+const STORE = 'designer-laboratory-sandbox.myshopify.com', API = '2024-10';
+const INV_LOCATION = 'gid://shopify/Location/5795643504'; // 15442 Ventura Blvd.
+const sleep = ms => new Promise(r => setTimeout(r, ms));
+const DATADIR = path.join(__dirname, 'data');
+const LOG = path.join(DATADIR, `remediate-${new Date().toISOString().replace(/[:.]/g, '-')}.jsonl`);
+function logRec(rec){ try{ fs.mkdirSync(DATADIR,{recursive:true}); fs.appendFileSync(LOG, JSON.stringify({...rec,ts:new Date().toISOString()})+'\n'); }catch{} }
+
+// ---- shopify (identical transport to cadence-import.js) ----
+function gql(query, variables) {
+ return new Promise(res => {
+ const data = JSON.stringify({ query, variables });
+ const req = https.request({ host: STORE, path: `/admin/api/${API}/graphql.json`, method: 'POST',
+ headers: { 'X-Shopify-Access-Token': TOKEN, 'Content-Type': 'application/json', 'Content-Length': Buffer.byteLength(data) } },
+ r => { let d=''; r.on('data',c=>d+=c); r.on('end',()=>{ try{res({status:r.statusCode,json:JSON.parse(d)})}catch{res({status:r.statusCode,raw:d.slice(0,400)})} }); });
+ req.on('error', e => res({ status: 0, err: e.message }));
+ req.write(data); req.end();
+ });
+}
+async function gqlRetry(q, v) {
+ for (let a=0;a<10;a++){
+ const r=await gql(q,v);
+ const errs = r.json && r.json.errors ? JSON.stringify(r.json.errors) : '';
+ if (r.status===429 || errs.includes('THROTTLED')) { await sleep(3000*(a+1)); continue; }
+ const avail = r.json?.extensions?.cost?.throttleStatus?.currentlyAvailable;
+ await sleep(avail != null && avail < 400 ? 1800 : 400);
+ return r;
+ }
+ return { status: 429, raw: 'throttled-exhausted' };
+}
+
+// ---- publish-to-ALL-channels (copied verbatim from cadence-import.js) ----
+const M_PUBLISH = `mutation($id:ID!,$input:[PublicationInput!]!){
+ publishablePublish(id:$id, input:$input){ userErrors{field message} }
+}`;
+let PUBLICATION_IDS = null;
+async function loadPublications() {
+ if (PUBLICATION_IDS) return PUBLICATION_IDS;
+ const r = await gqlRetry(`{publications(first:50){edges{node{id name}}}}`, {});
+ PUBLICATION_IDS = (r.json?.data?.publications?.edges || []).map(e => e.node);
+ return PUBLICATION_IDS;
+}
+async function publishToChannels(productId) {
+ const pubs = await loadPublications();
+ if (!pubs.length) return { published: false, why: 'no-publications-scope' };
+ const input = pubs.map(p => ({ publicationId: p.id }));
+ const r = await gqlRetry(M_PUBLISH, { id: productId, input });
+ const ue = (r.json?.data?.publishablePublish?.userErrors) || [];
+ const real = ue.filter(e => !/already|cannot be published to itself/i.test(e.message || ''));
+ if (real.length) return { published: false, errors: real };
+ return { published: true, channels: pubs.length };
+}
+
+// ---- inventory=2026 (copied verbatim from cadence-import.js) ----
+const INV_LOOKUP = `query($q:String!){productVariants(first:250,query:$q){edges{node{sku inventoryItem{id}}}}}`;
+const INV_SET = `mutation($input:InventorySetQuantitiesInput!){inventorySetQuantities(input:$input){userErrors{field message code}}}`;
+async function setInventory2026(skus) {
+ if (!skus.length) return { set: 0, errors: [] };
+ const map = new Map();
+ for (let i=0;i<skus.length;i+=40) {
+ const batch = skus.slice(i, i+40);
+ const q = batch.map(s => `sku:"${String(s).replace(/"/g,'\\"')}"`).join(' OR ');
+ const r = await gqlRetry(INV_LOOKUP, { q });
+ for (const e of (r.json?.data?.productVariants?.edges || [])) {
+ if (e.node.sku && e.node.inventoryItem?.id) map.set(e.node.sku, e.node.inventoryItem.id);
+ }
+ }
+ const pairs = skus.filter(s => map.has(s)).map(s => ({ inventoryItemId: map.get(s), locationId: INV_LOCATION, quantity: 2026 }));
+ const errors = [];
+ for (let i=0;i<pairs.length;i+=250) {
+ const r = await gqlRetry(INV_SET, { input: { name:'on_hand', reason:'correction', ignoreCompareQuantity:true, quantities: pairs.slice(i,i+250) } });
+ const ue = r.json?.data?.inventorySetQuantities?.userErrors || [];
+ if (ue.length) errors.push(...ue);
+ }
+ return { set: pairs.length, mapped: map.size, errors };
+}
+
+// ---- scan: iterate the cohort, classify each ACTIVE product ----
+const SCAN = `query($q:String!,$cursor:String){products(first:50,query:$q,sortKey:CREATED_AT,reverse:true,after:$cursor){pageInfo{hasNextPage endCursor}edges{node{id title status vendor resourcePublicationsCount{count} variants(first:10){edges{node{sku inventoryQuantity}}}}}}}`;
+
+(async () => {
+ if (!TOKEN) { console.error('no SHOPIFY_ADMIN_TOKEN'); process.exit(1); }
+ await loadPublications();
+ console.log(`Publications in scope: ${PUBLICATION_IDS.length} (expect ${TOTAL_CHANNELS})`);
+ const q = VENDOR ? `vendor:'${VENDOR}' status:active` : `status:active`;
+ const cap = VENDOR ? Infinity : RECENT;
+ console.log(`\nScope: ${VENDOR ? `ALL active for vendor '${VENDOR}'` : `last ${RECENT} active products (newest)`}`);
+ console.log(`Mode: ${COMMIT ? 'LIVE COMMIT' : 'DRY-RUN'}\n`);
+
+ let cursor=null, seen=0, needPublish=0, needInv=0, publishedOk=0, publishFail=0;
+ const skusToSet = []; // sellable (non-Sample) variant skus that are under 2026
+
+ while (seen < cap) {
+ const r = await gqlRetry(SCAN, { q, cursor });
+ const conn = r.json?.data?.products;
+ if (!conn) { console.log('SCAN ERR', JSON.stringify(r.json?.errors||r.raw||r.status).slice(0,200)); break; }
+ for (const e of conn.edges) {
+ if (seen >= cap) break;
+ seen++;
+ const n = e.node;
+ const chans = n.resourcePublicationsCount?.count || 0;
+ const sellable = n.variants.edges.map(x=>x.node).filter(x => !/-Sample$/i.test(x.sku||''));
+ const invOk = sellable.length > 0 && sellable.every(x => x.inventoryQuantity === 2026);
+ const underChannels = chans < TOTAL_CHANNELS;
+
+ if (underChannels) {
+ needPublish++;
+ if (COMMIT) {
+ const res = await publishToChannels(n.id);
+ if (res.published) { publishedOk++; logRec({action:'publish',id:n.id,vendor:n.vendor,channels:res.channels,ok:true}); }
+ else { publishFail++; console.log(` ⚠ publish fail ${n.id} ${JSON.stringify(res.errors||res.why).slice(0,120)}`); logRec({action:'publish',id:n.id,vendor:n.vendor,ok:false,err:res.errors||res.why}); }
+ if ((publishedOk) % 50 === 0 && publishedOk) process.stdout.write(`\r published ${publishedOk}…`);
+ }
+ }
+ if (!invOk) {
+ needInv++;
+ for (const v of sellable) if (v.sku && v.inventoryQuantity !== 2026) skusToSet.push(v.sku);
+ }
+ }
+ cursor = conn.pageInfo.hasNextPage ? conn.pageInfo.endCursor : null;
+ if (!cursor) break;
+ }
+
+ console.log(`\n\nScanned ${seen} active products.`);
+ console.log(` under 13 channels (need publish): ${needPublish}`);
+ console.log(` under 2026 inventory (need set): ${needInv} (${skusToSet.length} sellable variant skus)`);
+
+ if (COMMIT) {
+ console.log(`\nPublish results: ${publishedOk} published to all channels, ${publishFail} failed.`);
+ if (skusToSet.length) {
+ console.log(`\nSetting inventory on_hand=2026 at Ventura Blvd on ${skusToSet.length} sellable variant(s)…`);
+ const inv = await setInventory2026(skusToSet);
+ console.log(` inventory set: ${inv.set} pairs, mapped ${inv.mapped}, errors ${inv.errors.length}`);
+ if (inv.errors.length) console.log(' inv errors:', JSON.stringify(inv.errors).slice(0,300));
+ logRec({action:'inventory-batch',requested:skusToSet.length,set:inv.set,mapped:inv.mapped,errors:inv.errors.length});
+ } else {
+ console.log('\nNo inventory to set (all sellable variants already at 2026).');
+ }
+ console.log(`\nAudit log: ${LOG}`);
+ } else {
+ console.log('\nDRY-RUN — re-run with --commit to apply. No writes made.');
+ }
+})();
← e29dbe32 cadence: resume-check.sh — verify paced cadence resumes prod
·
back to Designer Wallcoverings
·
cadence wrapper: refund unused upload grant on 429/exit-3 + 8d58a8d4 →