[object Object]

← back to Designer Wallcoverings

DRAFT (no deploy): push-spec-metafields helper + activate-gated patch — push global.width/specs/mfr_sku metafields at activation (fixes width-metafield gap on ~500 actives)

080d706c497cec526f480ef9d3b532e85fe5ad83 · 2026-06-20 11:26:01 -0700 · Steve

Files touched

Diff

commit 080d706c497cec526f480ef9d3b532e85fe5ad83
Author: Steve <steve@designerwallcoverings.com>
Date:   Sat Jun 20 11:26:01 2026 -0700

    DRAFT (no deploy): push-spec-metafields helper + activate-gated patch — push global.width/specs/mfr_sku metafields at activation (fixes width-metafield gap on ~500 actives)
---
 .../cadence/activate-gated.metafield-push.PATCH.md | 97 ++++++++++++++++++++++
 1 file changed, 97 insertions(+)

diff --git a/shopify/scripts/cadence/activate-gated.metafield-push.PATCH.md b/shopify/scripts/cadence/activate-gated.metafield-push.PATCH.md
new file mode 100644
index 00000000..1278bfa2
--- /dev/null
+++ b/shopify/scripts/cadence/activate-gated.metafield-push.PATCH.md
@@ -0,0 +1,97 @@
+# PATCH (DRAFT, not applied) — push spec metafields at activation
+
+**What:** make `activate-gated.js` write the spec metafields (`global.width` + mirrors,
+length/repeat/material, `custom.manufacturer_sku`) from the vendor row it already has in
+hand **before** flipping ACTIVE — the vp-dw-commerce-approved "push-then-activate" default.
+**Why:** the gate validates width from the vendor row (`c.width`) but never pushes it as a
+metafield → ~500 ACTIVE products with width known in PG but no `global.width` on Shopify
+(and `custom.manufacturer_sku` missing on 497/499). Verified split: 174 confirmed
+recoverable from catalog source, 8 source-but-no-width, 317 unlinked by SKU — but ALL of
+them passed the gate on a vendor-row width, so the row was in scope at activation; pushing
+there fixes every future case at the source.
+**Status:** new helper `lib/push-spec-metafields.js` added (inert until wired). This file is
+the review diff — **not applied** to `activate-gated.js`, **no deploy**.
+
+---
+
+## Diff for `cadence/activate-gated.js`
+
+**1. require the helper (top, near the other requires ~line 32):**
+```js
+const { pushSpecMetafields } = require('../lib/push-spec-metafields');
+```
+
+**2. carry the live metafields onto the to-promote list** — the promote loop currently
+keeps only the gid, but the push needs the product's existing metafields (to avoid
+clobbering curated values). Change the `drafts.push(...)` at ~line 165:
+```js
+// before:
+if (gate.ok) drafts.push(n.id);
+// after:
+if (gate.ok) drafts.push({ gid: n.id, mfs });   // mfs already computed at ~line 144
+```
+
+**3. push-then-activate in the promote loop (~lines 170-177):**
+```js
+// before:
+for (const gid of drafts) {
+  const c = byGid.get(gid);
+  if (!COMMIT) { console.log(`    · would ACTIVATE ${c.dw_sku}`); continue; }
+  const r = await gqlRetry(ACTIVATE, { id: gid });
+  ...
+}
+
+// after:
+for (const d of drafts) {
+  const gid = d.gid;
+  const c = byGid.get(gid);
+  // PUSH spec metafields from the vendor row BEFORE activating (default path)
+  const push = await pushSpecMetafields(gid, c, d.mfs, gqlRetry, COMMIT);
+  if (push.errors) { console.log(`    ✗ ${c.dw_sku}: metafield push failed ${JSON.stringify(push.errors).slice(0,100)} — leaving DRAFT`); continue; }
+  if (!COMMIT) { console.log(`    · would push [${push.keys.join(', ')}] then ACTIVATE ${c.dw_sku}`); continue; }
+  if (push.pushed) { console.log(`    + ${c.dw_sku}: pushed ${push.keys.join(', ')}`); }
+  const r = await gqlRetry(ACTIVATE, { id: gid });
+  const ue = r.json?.data?.productUpdate?.userErrors;
+  if (ue && ue.length) { console.log(`    ✗ ${c.dw_sku}: ${JSON.stringify(ue).slice(0,120)}`); continue; }
+  promoted++;
+  fs.appendFileSync(AUDIT, JSON.stringify({ ts: new Date().toISOString(), vendor, dw_sku: c.dw_sku, product_id: gid, from: 'DRAFT', to: 'ACTIVE', pushed: push.keys }) + '\n');
+}
+```
+
+**Behaviour:** unchanged gate, unchanged DRY-RUN default. With `--commit` it now writes the
+width/specs/mfr-sku metafields (only the ones missing) immediately before ACTIVATE, so no
+new activation can land without a width metafield. Idempotent; never overwrites existing
+metafields (helper's `has()` guard).
+
+## Existing-500 backfill (separate, drip-paced)
+
+The forward fix only heals *new* promotions. For the ~500 already-ACTIVE gap items, run a
+sibling pass `backfill-spec-metafields.js` (DRY-RUN default, `--commit` gated, ≥90s between
+batches under the ~864/day variant cap — though metafield writes are NOT variants, so this
+is cap-safe; throttle only for API politeness):
+
+```
+for each vendor in VENDORS:
+  rows = candidates(table)               # same gate query — gives {pid, dw_sku, width, ...}
+  for batch of rows (50):
+    nodes = STATUS_Q(gids)               # pull live status + metafields
+    for n where n.status == 'ACTIVE' and no global/custom.width metafield:
+      pushSpecMetafields(n.id, row, n.metafields, gqlRetry, COMMIT)
+```
+
+**Coverage caveat (the 317):** `candidates()` only returns rows whose vendor table carries a
+`shopify_product_id` link — sparse on many tables (e.g. 11/1506 on tres_tintas). So the
+backfill cleanly covers the ~174 linked rows; the 317 unlinked actives need a one-time
+SKU-relink (Shopify `variants[].sku` / `custom.manufacturer_sku` → catalog `mfr_sku`) first.
+That relink is a separate read-only task — do it before the backfill to lift coverage.
+
+## Test plan (before any --commit)
+1. `node activate-gated.js --vendor Osborne` (DRY-RUN) → confirm it prints `would push [global.width, custom.width, custom.manufacturer_sku] then ACTIVATE …` for gap SKUs, and prints nothing-to-push for complete ones.
+2. `node activate-gated.js --vendor Osborne --limit 3 --commit` → 3 products; verify on Shopify the `global.width` metafield now matches the vendor row and the $4.25 sample + status are untouched.
+3. Re-run `shopify/activated-viewer/audit.js` → width% rises for the touched vendor.
+4. Then the backfill on a single vendor, same 3-step verify, before fleet-wide.
+
+## Gates (Steve)
+Live writes (the `--commit` runs, the deploy of the patched `activate-gated.js`, the backfill
+`--commit`) remain Steve-gated. This draft is files-only: the helper is inert, this patch is
+not applied, nothing deployed, no store writes.

← c48c81f4 run inventory-set-2026 sweep after each cadence run  ·  back to Designer Wallcoverings  ·  Osborne dry-run of metafield-push patch (0 writes): 2/2 heal 440ff0c3 →