← back to Designer Wallcoverings
TK-11337: add reversible bare mfr-SKU tag step + enforced SKU-purity guard (never raw mfr in dw-series sku)
9baa288680e4f04d21032ae2081945d6f8848b26 · 2026-09-09 15:39:32 -0700 · Steve
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VzZkMwx4e9ec829p24cAp3
Files touched
M scripts/versa-20oz-hollywood-fix/apply.mjs
Diff
commit 9baa288680e4f04d21032ae2081945d6f8848b26
Author: Steve <steve@designerwallcoverings.com>
Date: Wed Sep 9 15:39:32 2026 -0700
TK-11337: add reversible bare mfr-SKU tag step + enforced SKU-purity guard (never raw mfr in dw-series sku)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VzZkMwx4e9ec829p24cAp3
---
scripts/versa-20oz-hollywood-fix/apply.mjs | 40 ++++++++++++++++++++++++++++--
1 file changed, 38 insertions(+), 2 deletions(-)
diff --git a/scripts/versa-20oz-hollywood-fix/apply.mjs b/scripts/versa-20oz-hollywood-fix/apply.mjs
index 3882b734..59453cb8 100644
--- a/scripts/versa-20oz-hollywood-fix/apply.mjs
+++ b/scripts/versa-20oz-hollywood-fix/apply.mjs
@@ -67,7 +67,7 @@ function readManifest() {
}
const Q_READ = `query($id:ID!){ product(id:$id){
- id title status bodyHtml
+ id title status bodyHtml tags
options{ id name position }
uom: metafield(namespace:"global",key:"unit_of_measure"){id value}
dwsku: metafield(namespace:"global",key:"dw_sku"){id value}
@@ -104,6 +104,7 @@ function planFor(row, p) {
status: p.status,
bodyHtml: p.bodyHtml ?? '', // BUG-1 FIX: persist the RAW description so rollback can restore it
bodyHtml_had_specblock: alreadyAppended,
+ tags: Array.isArray(p.tags) ? p.tags.slice() : [], // FULL old tag set (reversible; append-only)
unit_of_measure: p.uom?.value ?? null,
dw_sku_mf: p.dwsku?.value ?? null,
v_prods_quantity_order_min: p.qmin?.value ?? null,
@@ -150,8 +151,31 @@ const M_BODY = `mutation($p:ProductInput!){ productUpdate(input:$p){ userErrors{
const M_REORDER = `mutation($pid:ID!,$moves:[ProductVariantPositionInput!]!){
productVariantsBulkReorder(productId:$pid, positions:$moves){ userErrors{field message} }}`;
+// SKU-PURITY INVARIANT (Steve HARD rule 2026-09-09: "create dw series skus. NEVER raw numbers from
+// mfr in the sku"). Asserts every minted identity is a clean DW-series XZW code and contains NEITHER
+// the raw mfr_sku NOR (if present) the internal momentum number. Throws on any violation so a future
+// manifest/code regression can NEVER write a SKU carrying a raw mfr code. Called per-product AND as an
+// apply() preflight over the whole restore-map (fail-fast before the batch starts).
+const XZW_RE = /^XZW-3\d{6}$/;
+function assertSkuPurity(plan) {
+ const dw = plan.new.dw_sku_mf || '';
+ const ySku = plan.new.sellable ? plan.new.sellable.sku : `${dw}-yard`;
+ const sSku = plan.new.sample ? plan.new.sample.sku : `${dw}-sample`;
+ const raws = [plan.mfr_sku, plan.momentum_sku].filter(Boolean).map((s) => String(s).toLowerCase());
+ const bad = [];
+ if (!XZW_RE.test(dw)) bad.push(`dw_sku "${dw}" not a clean XZW-3###### code`);
+ if (!XZW_RE.test((ySku || '').replace(/-yard$/, ''))) bad.push(`sellable sku "${ySku}" not {XZW}-yard`);
+ if (!XZW_RE.test((sSku || '').replace(/-sample$/, ''))) bad.push(`sample sku "${sSku}" not {XZW}-sample`);
+ for (const id of [dw, ySku, sSku]) {
+ const lo = String(id).toLowerCase();
+ for (const raw of raws) if (raw && lo.includes(raw)) bad.push(`identity "${id}" contains raw mfr/momentum "${raw}"`);
+ }
+ if (bad.length) throw new Error(`SKU_PURITY_VIOLATION pid=${plan.pid}: ${bad.join('; ')}`);
+}
+
async function applyOne(plan) {
const gid = plan.gid;
+ assertSkuPurity(plan); // never write a SKU carrying a raw mfr code
// 1) variant option relabel + SKUs (inventoryItem.sku per standing rule); price passed through unchanged.
// BUG-2 FIX: rename the SELLABLE variant's option value "Single Roll"->"Sold Per Yard" via optionValues.
// The Sample variant's optionValues are NOT passed, so its value ("Sample") is untouched and the two
@@ -183,6 +207,12 @@ async function applyOne(plan) {
if (plan.new.sellable && plan.old.sellable.position !== 1) moves.push({ id: plan.new.sellable.id, position: 1 });
if (plan.new.sample && plan.old.sample.position !== 2) moves.push({ id: plan.new.sample.id, position: 2 });
if (moves.length) await gql(M_REORDER, { pid: gid, moves });
+ // 5) ADDITIVE bare mfr-SKU tag (Steve 2026-09-09): the raw manufacturer code as a tag, NO prefix.
+ // Preserve ALL existing tags; append only if absent (dedupe); never clobber the set.
+ const oldTags = plan.old.tags || [];
+ if (plan.mfr_sku && !oldTags.includes(plan.mfr_sku)) {
+ await gql(M_BODY, { p: { id: gid, tags: [...oldTags, plan.mfr_sku] } });
+ }
ledger(plan);
}
@@ -208,7 +238,11 @@ async function apply() {
const plans = all.filter((p) => p.old.sellable);
if (skipped.length) fs.writeFileSync(path.join(DATA, 'skipped-sample-only.json'),
JSON.stringify(skipped.map((p) => ({ pid: p.pid, handle: p.handle, mfr_sku: p.mfr_sku })), null, 2));
- process.stderr.write(`applying ${plans.length}; skipped ${skipped.length} sample-only\n`);
+ // PREFLIGHT: assert SKU purity across the WHOLE set before writing a single product (fail-fast).
+ const violations = [];
+ for (const p of plans) { try { assertSkuPurity(p); } catch (e) { violations.push(e.message); } }
+ if (violations.length) throw new Error(`ABORT — ${violations.length} SKU-purity violations:\n` + violations.slice(0, 20).join('\n'));
+ process.stderr.write(`SKU-purity preflight PASS (${plans.length} clean); skipped ${skipped.length} sample-only\n`);
let n = 0;
for (const plan of plans) {
try { await applyOne(plan); }
@@ -238,6 +272,8 @@ async function rollbackOne(plan) {
if (moves.length) await gql(M_REORDER, { pid: gid, moves });
// 3) body_html: restore the EXACT captured old body (only if the fix appended the block)
if (!plan.old.bodyHtml_had_specblock) await gql(M_BODY, { p: { id: gid, bodyHtml: plan.old.bodyHtml || '' } });
+ // 3b) tags: restore the EXACT captured old tag set (reverses the appended bare mfr-SKU tag)
+ await gql(M_BODY, { p: { id: gid, tags: plan.old.tags || [] } });
// 4) metafields: set back to old value if it existed; DELETE (by identifier) the ones the fix created
const setBack = [], del = [];
const spec = [
← 64dc9e71 auto-data-snapshot: 2026-09-09T15:36:04 (2 data files) — scr
·
back to Designer Wallcoverings
·
TK-11337: 6-digit XZW series (510985+, next-in-line), dw_sku 51bb5c9b →