← back to Designerwallcoverings
TK-11076: PJ retag halted at canary gate (stopped-canary-unsafe) — canary + rollback executor
672d3196781f66331a8d64448e635d15599431a3 · 2026-09-01 12:35:31 -0700 · Steve Abrams
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Files touched
A scripts/pj-onboard/out/tk11076-canary-diff.jsonA scripts/pj-onboard/tk11076-canary.mjsA scripts/pj-onboard/tk11076-rollback.mjs
Diff
commit 672d3196781f66331a8d64448e635d15599431a3
Author: Steve Abrams <steve@designerwallcoverings.com>
Date: Tue Sep 1 12:35:31 2026 -0700
TK-11076: PJ retag halted at canary gate (stopped-canary-unsafe) — canary + rollback executor
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---
scripts/pj-onboard/out/tk11076-canary-diff.json | 274 ++++++++++++++++++++++++
scripts/pj-onboard/tk11076-canary.mjs | 65 ++++++
scripts/pj-onboard/tk11076-rollback.mjs | 56 +++++
3 files changed, 395 insertions(+)
diff --git a/scripts/pj-onboard/out/tk11076-canary-diff.json b/scripts/pj-onboard/out/tk11076-canary-diff.json
new file mode 100644
index 0000000..19e8b1c
--- /dev/null
+++ b/scripts/pj-onboard/out/tk11076-canary-diff.json
@@ -0,0 +1,274 @@
+[
+ {
+ "handle": "dwjp-10101-philip-jeffries-mirage-blue-apparition-on-gesso-vinyl-canvas-new-10486",
+ "status": "ACTIVE",
+ "title": "Mirage-Blue Apparition on Gesso Vinyl Canvas - New",
+ "pattern": "Mirage-Blue Apparition on Gesso Vinyl Canvas",
+ "color_name_anchor": "(empty)",
+ "color_primary": "(empty)",
+ "resolved_colorway": "Blue Apparition on Gesso Vinyl Canvas",
+ "source": "pattern dash-tail",
+ "tags_before": [
+ "10486",
+ "Architectural",
+ "Bedroom",
+ "Brown",
+ "browse-hidden",
+ "Chocolate Brown",
+ "Class A Fire Rated",
+ "Commercial",
+ "Cream",
+ "display_variant",
+ "grasscloth",
+ "Grasscloth Texture",
+ "Grasscloth Wallcovering",
+ "Living Room",
+ "Luxury",
+ "Mirage-Blue Apparition on Gesso Vinyl Canvas - NEW",
+ "Natural Wallcovering",
+ "Office",
+ "Organic Modern",
+ "Paper",
+ "Phillip Jeffries",
+ "quotes",
+ "Showroom Line",
+ "Showroom Only",
+ "Solid",
+ "Sophisticated",
+ "Textured",
+ "Timeless",
+ "Traditional",
+ "Vinyl",
+ "Wallcovering",
+ "White"
+ ],
+ "would_strip": [
+ "Brown",
+ "Chocolate Brown",
+ "Cream",
+ "White"
+ ],
+ "legit_color_tags_at_risk": []
+ },
+ {
+ "handle": "hyptonic-hemp-by-phillip-jeffries-at-the-dw-showroom",
+ "status": "ARCHIVED",
+ "title": "Hyptonic Hemp by Phillip Jeffries at the DW Showroom",
+ "color_name_anchor": "(empty)",
+ "color_primary": "(empty)",
+ "resolved_colorway": null,
+ "source": "UNRESOLVABLE",
+ "tags_before": [
+ "Beige",
+ "browse-hidden",
+ "Coral",
+ "display_variant",
+ "grasscloth",
+ "Grasscloth Texture",
+ "Grasscloth Wallcovering",
+ "Maroon",
+ "Navy",
+ "Ombre",
+ "Ombre Graasscloth",
+ "Plum",
+ "Salmon",
+ "Showroom Only",
+ "Teal",
+ "Wallcovering"
+ ],
+ "would_strip": [
+ "Beige",
+ "Coral",
+ "Maroon",
+ "Navy",
+ "Plum",
+ "Salmon",
+ "Teal"
+ ],
+ "legit_color_tags_at_risk": []
+ },
+ {
+ "handle": "untethered-by-phillip-jeffries",
+ "status": "ACTIVE",
+ "title": "Untethered - Black by Phillip Jeffries at the DW Showroom",
+ "color_name_anchor": "(empty)",
+ "color_primary": "(empty)",
+ "resolved_colorway": null,
+ "source": "UNRESOLVABLE",
+ "tags_before": [
+ "Abstract",
+ "AI-Analyzed-v2",
+ "Architectural",
+ "Black",
+ "browse-hidden",
+ "Class A Fire Rated",
+ "Cocoa",
+ "Commercial",
+ "Contemporary",
+ "Cream",
+ "Custom",
+ "display_variant",
+ "Gold",
+ "GOLD LEAF",
+ "Luxury",
+ "Organic Modern",
+ "Paper",
+ "Phillip Jeffries",
+ "quotes",
+ "Showroom Line",
+ "Showroom Only",
+ "Untethered",
+ "Wallcovering",
+ "Yellow"
+ ],
+ "would_strip": [
+ "Black",
+ "Cocoa",
+ "Cream",
+ "Gold",
+ "Yellow"
+ ],
+ "legit_color_tags_at_risk": []
+ },
+ {
+ "handle": "dwjp-10003-philip-jeffries-palm-paradise-golden-jungle-new-10563",
+ "status": "ACTIVE",
+ "title": "Palm Paradise-Golden Jungle - New",
+ "color_name_anchor": "(empty)",
+ "color_primary": "(empty)",
+ "resolved_colorway": null,
+ "source": "UNRESOLVABLE",
+ "tags_before": [
+ "10563",
+ "Almond",
+ "Architectural",
+ "Bedroom",
+ "browse-hidden",
+ "Camel",
+ "Coastal",
+ "Commercial",
+ "Cream",
+ "display_variant",
+ "Living Room",
+ "Luxury",
+ "Mahogany",
+ "Office",
+ "Palm Paradise-Golden Jungle - NEW",
+ "Paper",
+ "Pattern",
+ "Phillip Jeffries",
+ "Phillip Jeffries Wallcovering",
+ "quotes",
+ "Saddle Brown",
+ "Showroom Line",
+ "Solid",
+ "Sophisticated",
+ "Timeless",
+ "Traditional",
+ "Tropical",
+ "Wallcovering",
+ "White"
+ ],
+ "would_strip": [
+ "Almond",
+ "Camel",
+ "Cream",
+ "Mahogany",
+ "Saddle Brown",
+ "White"
+ ],
+ "legit_color_tags_at_risk": []
+ },
+ {
+ "handle": "dwjp-10007-philip-jeffries-braided-raffia-natural-essence-new-10298",
+ "status": "ACTIVE",
+ "title": "Braided Raffia-Natural Essence - New",
+ "color_name_anchor": "(empty)",
+ "color_primary": "(empty)",
+ "resolved_colorway": null,
+ "source": "UNRESOLVABLE",
+ "tags_before": [
+ "Architectural",
+ "Bedroom",
+ "Braided Raffia-Natural Essence - NEW",
+ "Brown",
+ "browse-hidden",
+ "Chocolate Brown",
+ "Commercial",
+ "Cream",
+ "display_variant",
+ "Grasscloth",
+ "Living Room",
+ "Luxury",
+ "Natural Wallcovering",
+ "Office",
+ "Paper",
+ "Phillip Jeffries",
+ "quotes",
+ "Raffia",
+ "Rustic",
+ "Showroom Line",
+ "Showroom Only",
+ "Solid",
+ "Sophisticated",
+ "Textured",
+ "Timeless",
+ "Traditional",
+ "Wallcovering",
+ "White"
+ ],
+ "would_strip": [
+ "Brown",
+ "Chocolate Brown",
+ "Cream",
+ "White"
+ ],
+ "legit_color_tags_at_risk": []
+ },
+ {
+ "handle": "dwjp-10008-philip-jeffries-braided-raffia-arctic-twist-new-10300",
+ "status": "ACTIVE",
+ "title": "Braided Raffia-Arctic Twist - New",
+ "color_name_anchor": "(empty)",
+ "color_primary": "(empty)",
+ "resolved_colorway": null,
+ "source": "UNRESOLVABLE",
+ "tags_before": [
+ "Architectural",
+ "Bedroom",
+ "Braided Raffia-Arctic Twist - NEW",
+ "Brown",
+ "browse-hidden",
+ "Chocolate Brown",
+ "Commercial",
+ "Cream",
+ "display_variant",
+ "Grasscloth",
+ "Living Room",
+ "Luxury",
+ "Natural Wallcovering",
+ "Office",
+ "Paper",
+ "Phillip Jeffries",
+ "quotes",
+ "Raffia",
+ "Rustic",
+ "Showroom Line",
+ "Showroom Only",
+ "Solid",
+ "Sophisticated",
+ "Textured",
+ "Timeless",
+ "Traditional",
+ "Wallcovering",
+ "White"
+ ],
+ "would_strip": [
+ "Brown",
+ "Chocolate Brown",
+ "Cream",
+ "White"
+ ],
+ "legit_color_tags_at_risk": []
+ }
+]
diff --git a/scripts/pj-onboard/tk11076-canary.mjs b/scripts/pj-onboard/tk11076-canary.mjs
new file mode 100644
index 0000000..bf0b23d
--- /dev/null
+++ b/scripts/pj-onboard/tk11076-canary.mjs
@@ -0,0 +1,65 @@
+#!/usr/bin/env node
+/**
+ * TK-11076 — Phillip Jeffries CANARY. READ-ONLY. No writes.
+ * For 6 diverse live PJ products, resolve the real colorway from the anchor
+ * pj_catalog.color_name (spec anchor), and — where color_name is empty — from
+ * the Pattern-Colorway dash-tail of pattern_name. Then compute the tag strip
+ * the batch WOULD apply, and JUDGE whether the strip is safe (i.e. whether we
+ * can cleanly separate AI-palette color-word strays from legit descriptive
+ * color tags). PJ has NO per-product AI-palette source (ai_colors /
+ * ai_background_color / ai_tags are 100% NULL), so the only strip rule
+ * available is a GLOBAL bare-color-word denylist — which the osborne/sanderson
+ * remediation explicitly rejected as unsafe.
+ */
+import { execSync } from 'node:child_process';
+const DB='postgresql:///dw_unified?host=/tmp';
+const STORE=process.env.SHOPIFY_STORE||'designer-laboratory-sandbox.myshopify.com';
+const TOKEN=process.env.SHOPIFY_ADMIN_TOKEN;
+const API='2024-10';
+const sleep=ms=>new Promise(r=>setTimeout(r,ms));
+async function gql(q,v){const res=await fetch(`https://${STORE}/admin/api/${API}/graphql.json`,{method:'POST',headers:{'X-Shopify-Access-Token':TOKEN,'Content-Type':'application/json'},body:JSON.stringify({query:q,variables:v})});return (await res.json()).data;}
+const decode=s=>String(s||'').replace(/&/g,'&').replace(/'/g,"'").replace(/"/g,'"');
+const titleCase=s=>decode(s).replace(/\b\w/g,c=>c.toUpperCase()).trim();
+const norm=s=>titleCase(s).toLowerCase().replace(/\s*\/\s*/g,'/').replace(/\s+/g,' ').trim();
+
+// pull pj_catalog rows keyed by shopify_product_id (the reliable join)
+function pjMap(){
+ const raw=execSync(`psql "${DB}" -tAc "select coalesce(json_agg(json_build_object('spid',shopify_product_id,'pat',pattern_name,'color',color_name,'cp',color_primary))::text,'[]') from pj_catalog"`,{encoding:'utf8',maxBuffer:128*1024*1024}).trim();
+ const m=new Map();
+ for(const r of JSON.parse(raw||'[]')) if(r.spid) m.set(String(r.spid),r);
+ return m;
+}
+async function main(){
+ const pj=pjMap();
+ // 6 diverse: a clean-color_name product, a dash-tail product, an all-caps GOLD LEAF, an ombre, a grasscloth, an archived
+ const d=await gql(`query{products(first:250, query:"vendor:'Phillip Jeffries'"){nodes{id handle status title tags variants(first:2){nodes{sku}}}}}`);
+ const nodes=d.products.nodes;
+ const withColorName=nodes.find(n=>{const r=pj.get(n.id.split('/').pop());return r&&r.color&&r.color.length<40;});
+ const dashTail=nodes.find(n=>{const r=pj.get(n.id.split('/').pop());return r&&(!r.color)&&r.pat&&r.pat.includes('-');});
+ const heavyPalette=nodes.find(n=>n.tags.filter(t=>/^(black|white|cream|gold|beige|navy|teal|coral|plum|almond|camel|mahogany)$/i.test(t)).length>=4);
+ const pick=[...new Set([withColorName,dashTail,heavyPalette,...nodes].filter(Boolean))].slice(0,6);
+ const report=[];
+ const DENY=new Set(['white','black','grey','gray','ivory','cream','beige','stone','taupe','greige','oatmeal','silver','gold','bronze','copper','pewter','charcoal','slate','teal','aqua','turquoise','jade','sage','green','blue','navy','indigo','cobalt','red','burgundy','coral','salmon','blush','pink','rose','lavender','purple','plum','mauve','yellow','ochre','mustard','amber','peach','tan','walnut','brown','chocolate','khaki','almond','camel','mahogany','cocoa','maroon','saddle brown','chocolate brown']);
+ for(const p of pick){
+ const r=pj.get(p.id.split('/').pop())||{};
+ let colorway=r.color&&r.color.length<40&&!/repeat:|panel|mural|cm\)/i.test(r.color)?r.color:null;
+ let source='color_name';
+ if(!colorway&&r.pat&&r.pat.includes('-')){colorway=r.pat.split('-').slice(1).join('-').trim()||null;source='pattern dash-tail';}
+ if(!colorway)source='UNRESOLVABLE';
+ // simulate strip: remove bare color-word tags NOT equal to colorway; add color:<colorway>
+ const cwNorm=colorway?norm(colorway):null;
+ const wouldStrip=[], legitAtRisk=[];
+ for(const t of p.tags){
+ if(DENY.has(norm(t))&&(!cwNorm||norm(t)!==cwNorm)){
+ wouldStrip.push(t);
+ // Is this stray ACTUALLY a legit color of the product? We have NO per-product palette to tell.
+ // color_primary is the only signal (and it's empty for the affected set).
+ if(r.cp&&norm(t)===norm(r.cp)) legitAtRisk.push(t+' (==color_primary, LEGIT)');
+ }
+ }
+ report.push({handle:p.handle,status:p.status,title:p.title,pattern:r.pat,color_name_anchor:r.color||'(empty)',color_primary:r.cp||'(empty)',resolved_colorway:colorway,source,tags_before:p.tags,would_strip:wouldStrip,legit_color_tags_at_risk:legitAtRisk});
+ await sleep(250);
+ }
+ console.log(JSON.stringify(report,null,2));
+}
+main();
diff --git a/scripts/pj-onboard/tk11076-rollback.mjs b/scripts/pj-onboard/tk11076-rollback.mjs
new file mode 100644
index 0000000..d0b141b
--- /dev/null
+++ b/scripts/pj-onboard/tk11076-rollback.mjs
@@ -0,0 +1,56 @@
+#!/usr/bin/env node
+/**
+ * TK-11076 — Phillip Jeffries rollback executor.
+ *
+ * The TK-11076 batch was HALTED at the canary gate (stopped-canary-unsafe): no
+ * product tags were ever modified, so this rollback has nothing to undo. It is
+ * present per the one-file-per-vendor rollback rule and is fully functional: if
+ * a future authorized PJ retag ever populates the rollback map with per-handle
+ * {old_tags} entries, this restores each handle's tags to old_tags.
+ *
+ * node tk11076-rollback.mjs # DRY-RUN: list what WOULD be restored
+ * node tk11076-rollback.mjs --only=h1,h2 # scope to specific handles
+ * node tk11076-rollback.mjs --apply # restore old_tags on Shopify
+ */
+import fs from 'node:fs';
+import path from 'node:path';
+
+const STORE = process.env.SHOPIFY_STORE || 'designer-laboratory-sandbox.myshopify.com';
+const TOKEN = process.env.SHOPIFY_ADMIN_TOKEN;
+const API = process.env.SHOPIFY_API_VERSION || '2024-10';
+const APPLY = process.argv.includes('--apply');
+const ONLY = ((process.argv.find(a => a.startsWith('--only=')) || '').split('=')[1] || '').split(',').filter(Boolean);
+const ROLLBACK = path.join(process.env.HOME, '.claude/yolo-queue/executed-reversible/TK-11076-Phillip_Jeffries-rollback.json');
+const sleep = ms => new Promise(r => setTimeout(r, ms));
+if (!TOKEN) { console.error('FATAL: set SHOPIFY_ADMIN_TOKEN'); process.exit(1); }
+
+async function gql(q, v) {
+ const res = await fetch(`https://${STORE}/admin/api/${API}/graphql.json`, {
+ method: 'POST', headers: { 'X-Shopify-Access-Token': TOKEN, 'Content-Type': 'application/json' },
+ body: JSON.stringify({ query: q, variables: v }) });
+ return (await res.json()).data;
+}
+
+async function main() {
+ if (!fs.existsSync(ROLLBACK)) { console.error(`no rollback file: ${ROLLBACK}`); process.exit(1); }
+ const map = JSON.parse(fs.readFileSync(ROLLBACK, 'utf8'));
+ const entries = map.entries || {};
+ let handles = Object.keys(entries);
+ if (ONLY.length) handles = handles.filter(h => ONLY.includes(h));
+ if (!handles.length) {
+ console.log(`Nothing to roll back (status=${map.status}). No per-handle entries — the TK-11076 batch was halted at the canary gate, so no tags were changed.`);
+ return;
+ }
+ for (const h of handles) {
+ const e = entries[h];
+ console.log(`${APPLY ? 'RESTORE' : 'DRY'} ${h} → old_tags(${(e.old_tags || []).length})`);
+ if (APPLY) {
+ const d = await gql(`mutation($input:ProductInput!){ productUpdate(input:$input){ userErrors{ message } } }`,
+ { input: { id: e.id, tags: e.old_tags } });
+ const ue = d && d.productUpdate && d.productUpdate.userErrors;
+ if (ue && ue.length) console.error(` userErrors ${h}: ${JSON.stringify(ue)}`);
+ await sleep(500);
+ }
+ }
+}
+main();
← c3c3b07 TK-11073 Wave2: Stout LIVE remediation — 180 products retagg
·
back to Designerwallcoverings
·
TK-11076 China Seas retag: canary-first remediate+rollback s ce61a40 →