[object Object]

← back to Designerwallcoverings

TK-11076: title-anchored colorway parser + fleet retag driver + rollback (canary DRY-RUN, no Shopify writes)

801f3c51885a45df75dd62b5adf792956d1bcadf · 2026-09-01 13:32:23 -0700 · Steve Abrams

- parse-colorway.mjs: reusable mfr-colorway parser, both grammars (dash ' By <V>', pipe ' | <V>',
  comma-form), material-suffix + marketing-tail strip, confidence-gate SKIP on fused/no-delimiter
  titles. 28/28 self-test.
- tk11076-colorway-retag.mjs: DRY-RUN/canary driver -> custom.real_color_name metafield + color:<cw>
  tag; strips only per-product AI-palette color strays; per-vendor rollback file; per-item verify;
  90s gap/25 throttle on --apply (gated, not run this pass).
- Canary: 26 examined, 23 WRITE, 3 SKIP. Named checks pass (Cole&Son Charcoal, RebelWalls Rust,
  Aquila Rust->SKIP). Fleet: 9155/18062 confident, 8907 SKIP.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

Files touched

Diff

commit 801f3c51885a45df75dd62b5adf792956d1bcadf
Author: Steve Abrams <steve@designerwallcoverings.com>
Date:   Tue Sep 1 13:32:23 2026 -0700

    TK-11076: title-anchored colorway parser + fleet retag driver + rollback (canary DRY-RUN, no Shopify writes)
    
    - parse-colorway.mjs: reusable mfr-colorway parser, both grammars (dash ' By <V>', pipe ' | <V>',
      comma-form), material-suffix + marketing-tail strip, confidence-gate SKIP on fused/no-delimiter
      titles. 28/28 self-test.
    - tk11076-colorway-retag.mjs: DRY-RUN/canary driver -> custom.real_color_name metafield + color:<cw>
      tag; strips only per-product AI-palette color strays; per-vendor rollback file; per-item verify;
      90s gap/25 throttle on --apply (gated, not run this pass).
    - Canary: 26 examined, 23 WRITE, 3 SKIP. Named checks pass (Cole&Son Charcoal, RebelWalls Rust,
      Aquila Rust->SKIP). Fleet: 9155/18062 confident, 8907 SKIP.
    
    Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---
 .../out/tk11076-addressable-by-vendor.json         | 585 ++++++++++++++++++++
 .../out/tk11076-canary-before-after.txt            | 217 ++++++++
 .../out/tk11076-canary-mixed-diff.json             | 612 +++++++++++++++++++++
 .../out/tk11076-nonchromatic-tails.json            |  32 ++
 scripts/colorway-title-anchor/parse-colorway.mjs   | 173 ++++++
 .../tk11076-colorway-retag.mjs                     | 304 ++++++++++
 scripts/colorway-title-anchor/tk11076-rollback.mjs |  33 ++
 7 files changed, 1956 insertions(+)

diff --git a/scripts/colorway-title-anchor/out/tk11076-addressable-by-vendor.json b/scripts/colorway-title-anchor/out/tk11076-addressable-by-vendor.json
new file mode 100644
index 0000000..cc774c5
--- /dev/null
+++ b/scripts/colorway-title-anchor/out/tk11076-addressable-by-vendor.json
@@ -0,0 +1,585 @@
+{
+  "total": 18062,
+  "write": 9155,
+  "skip": 8907,
+  "reasons": {
+    "no-delimiter": 8836,
+    "too-long": 71
+  },
+  "forms": {
+    "dash": 8545,
+    "comma": 610
+  },
+  "per_vendor": [
+    {
+      "vendor": "Phillipe Romano",
+      "write": 428,
+      "skip": 1949,
+      "total": 2377
+    },
+    {
+      "vendor": "China Seas",
+      "write": 568,
+      "skip": 928,
+      "total": 1496
+    },
+    {
+      "vendor": "Rebel Walls",
+      "write": 546,
+      "skip": 916,
+      "total": 1462
+    },
+    {
+      "vendor": "Phillip Jeffries",
+      "write": 1046,
+      "skip": 166,
+      "total": 1212
+    },
+    {
+      "vendor": "Coordonné",
+      "write": 0,
+      "skip": 875,
+      "total": 875
+    },
+    {
+      "vendor": "Kravet",
+      "write": 711,
+      "skip": 94,
+      "total": 805
+    },
+    {
+      "vendor": "Malibu Wallpaper",
+      "write": 674,
+      "skip": 41,
+      "total": 715
+    },
+    {
+      "vendor": "Hollywood Wallcoverings",
+      "write": 343,
+      "skip": 283,
+      "total": 626
+    },
+    {
+      "vendor": "Cole & Son",
+      "write": 585,
+      "skip": 1,
+      "total": 586
+    },
+    {
+      "vendor": "DW Bespoke Studio",
+      "write": 88,
+      "skip": 495,
+      "total": 583
+    },
+    {
+      "vendor": "Osborne & Little",
+      "write": 1,
+      "skip": 499,
+      "total": 500
+    },
+    {
+      "vendor": "Scalamandre Wallpaper",
+      "write": 477,
+      "skip": 0,
+      "total": 477
+    },
+    {
+      "vendor": "Sandberg",
+      "write": 103,
+      "skip": 323,
+      "total": 426
+    },
+    {
+      "vendor": "Designer Wallcoverings",
+      "write": 109,
+      "skip": 297,
+      "total": 406
+    },
+    {
+      "vendor": "William Morris",
+      "write": 0,
+      "skip": 338,
+      "total": 338
+    },
+    {
+      "vendor": "Anna French",
+      "write": 331,
+      "skip": 0,
+      "total": 331
+    },
+    {
+      "vendor": "Harlequin",
+      "write": 325,
+      "skip": 0,
+      "total": 325
+    },
+    {
+      "vendor": "Maya Romanoff",
+      "write": 313,
+      "skip": 7,
+      "total": 320
+    },
+    {
+      "vendor": "Mind the Gap",
+      "write": 194,
+      "skip": 94,
+      "total": 288
+    },
+    {
+      "vendor": "Romo",
+      "write": 283,
+      "skip": 0,
+      "total": 283
+    },
+    {
+      "vendor": "Surface Stick",
+      "write": 278,
+      "skip": 0,
+      "total": 278
+    },
+    {
+      "vendor": "Glitter Walls",
+      "write": 228,
+      "skip": 0,
+      "total": 228
+    },
+    {
+      "vendor": "Jeffrey Stevens",
+      "write": 0,
+      "skip": 210,
+      "total": 210
+    },
+    {
+      "vendor": "Versace",
+      "write": 24,
+      "skip": 184,
+      "total": 208
+    },
+    {
+      "vendor": "Gaston Y Daniela",
+      "write": 203,
+      "skip": 0,
+      "total": 203
+    },
+    {
+      "vendor": "Clarke And Clarke",
+      "write": 190,
+      "skip": 0,
+      "total": 190
+    },
+    {
+      "vendor": "Graham & Brown",
+      "write": 1,
+      "skip": 177,
+      "total": 178
+    },
+    {
+      "vendor": "Innovations USA",
+      "write": 0,
+      "skip": 157,
+      "total": 157
+    },
+    {
+      "vendor": "Designers Guild",
+      "write": 5,
+      "skip": 117,
+      "total": 122
+    },
+    {
+      "vendor": "Ralph Lauren",
+      "write": 113,
+      "skip": 0,
+      "total": 113
+    },
+    {
+      "vendor": "Nina Campbell",
+      "write": 104,
+      "skip": 0,
+      "total": 104
+    },
+    {
+      "vendor": "British Walls",
+      "write": 93,
+      "skip": 9,
+      "total": 102
+    },
+    {
+      "vendor": "Mid Century Modern",
+      "write": 0,
+      "skip": 97,
+      "total": 97
+    },
+    {
+      "vendor": "Wolf Gordon",
+      "write": 93,
+      "skip": 1,
+      "total": 94
+    },
+    {
+      "vendor": "GP & J Baker",
+      "write": 93,
+      "skip": 0,
+      "total": 93
+    },
+    {
+      "vendor": "Marburg",
+      "write": 72,
+      "skip": 19,
+      "total": 91
+    },
+    {
+      "vendor": "Brunschwig & Fils",
+      "write": 78,
+      "skip": 0,
+      "total": 78
+    },
+    {
+      "vendor": "Fentucci",
+      "write": 48,
+      "skip": 20,
+      "total": 68
+    },
+    {
+      "vendor": "Brand McKenzie",
+      "write": 15,
+      "skip": 48,
+      "total": 63
+    },
+    {
+      "vendor": "Pixels",
+      "write": 57,
+      "skip": 0,
+      "total": 57
+    },
+    {
+      "vendor": "Laura Ashley Wallpaper",
+      "write": 0,
+      "skip": 53,
+      "total": 53
+    },
+    {
+      "vendor": "Ultrasuede",
+      "write": 10,
+      "skip": 36,
+      "total": 46
+    },
+    {
+      "vendor": "MC Escher Wallpaper",
+      "write": 0,
+      "skip": 46,
+      "total": 46
+    },
+    {
+      "vendor": "Luxury Murals",
+      "write": 0,
+      "skip": 42,
+      "total": 42
+    },
+    {
+      "vendor": "Hospitality Wallcoverings",
+      "write": 0,
+      "skip": 40,
+      "total": 40
+    },
+    {
+      "vendor": "Steve Abrams Studios",
+      "write": 18,
+      "skip": 21,
+      "total": 39
+    },
+    {
+      "vendor": "Donghia",
+      "write": 37,
+      "skip": 0,
+      "total": 37
+    },
+    {
+      "vendor": "Sancar",
+      "write": 0,
+      "skip": 37,
+      "total": 37
+    },
+    {
+      "vendor": "Thibaut",
+      "write": 16,
+      "skip": 20,
+      "total": 36
+    },
+    {
+      "vendor": "Roberto Cavalli Wallpaper",
+      "write": 0,
+      "skip": 35,
+      "total": 35
+    },
+    {
+      "vendor": "DW Home",
+      "write": 12,
+      "skip": 23,
+      "total": 35
+    },
+    {
+      "vendor": "Nobilis",
+      "write": 3,
+      "skip": 31,
+      "total": 34
+    },
+    {
+      "vendor": "Marimekko Exclusive",
+      "write": 0,
+      "skip": 33,
+      "total": 33
+    },
+    {
+      "vendor": "Elitis",
+      "write": 0,
+      "skip": 30,
+      "total": 30
+    },
+    {
+      "vendor": "Daisy Bennett",
+      "write": 28,
+      "skip": 0,
+      "total": 28
+    },
+    {
+      "vendor": "Mulberry",
+      "write": 28,
+      "skip": 0,
+      "total": 28
+    },
+    {
+      "vendor": "Natural Paint",
+      "write": 26,
+      "skip": 0,
+      "total": 26
+    },
+    {
+      "vendor": "Threads",
+      "write": 25,
+      "skip": 0,
+      "total": 25
+    },
+    {
+      "vendor": "Architectural Fabrics",
+      "write": 23,
+      "skip": 0,
+      "total": 23
+    },
+    {
+      "vendor": "Groundworks",
+      "write": 21,
+      "skip": 0,
+      "total": 21
+    },
+    {
+      "vendor": "Lee Jofa",
+      "write": 20,
+      "skip": 0,
+      "total": 20
+    },
+    {
+      "vendor": "Retro Walls",
+      "write": 9,
+      "skip": 6,
+      "total": 15
+    },
+    {
+      "vendor": "Missoni Wallpaper",
+      "write": 13,
+      "skip": 2,
+      "total": 15
+    },
+    {
+      "vendor": "Madagascar Walls",
+      "write": 11,
+      "skip": 2,
+      "total": 13
+    },
+    {
+      "vendor": "Traditional Whimsy",
+      "write": 2,
+      "skip": 10,
+      "total": 12
+    },
+    {
+      "vendor": "LA Walls",
+      "write": 0,
+      "skip": 12,
+      "total": 12
+    },
+    {
+      "vendor": "Franquemont London",
+      "write": 0,
+      "skip": 11,
+      "total": 11
+    },
+    {
+      "vendor": "Glambeads by DW",
+      "write": 0,
+      "skip": 10,
+      "total": 10
+    },
+    {
+      "vendor": "Los Angeles Fabrics",
+      "write": 9,
+      "skip": 0,
+      "total": 9
+    },
+    {
+      "vendor": "Hand Crafted",
+      "write": 2,
+      "skip": 7,
+      "total": 9
+    },
+    {
+      "vendor": "Koroseal",
+      "write": 9,
+      "skip": 0,
+      "total": 9
+    },
+    {
+      "vendor": "Graduate Collection",
+      "write": 0,
+      "skip": 8,
+      "total": 8
+    },
+    {
+      "vendor": "Arte International",
+      "write": 4,
+      "skip": 3,
+      "total": 7
+    },
+    {
+      "vendor": "Glass Beaded",
+      "write": 3,
+      "skip": 3,
+      "total": 6
+    },
+    {
+      "vendor": "Dolce & Gabbana",
+      "write": 0,
+      "skip": 6,
+      "total": 6
+    },
+    {
+      "vendor": "Hollywood Acoustical",
+      "write": 0,
+      "skip": 5,
+      "total": 5
+    },
+    {
+      "vendor": "Graduate Collection UK",
+      "write": 2,
+      "skip": 2,
+      "total": 4
+    },
+    {
+      "vendor": "Vahallan",
+      "write": 0,
+      "skip": 4,
+      "total": 4
+    },
+    {
+      "vendor": "Contrado",
+      "write": 0,
+      "skip": 4,
+      "total": 4
+    },
+    {
+      "vendor": "Primo Leathers",
+      "write": 0,
+      "skip": 3,
+      "total": 3
+    },
+    {
+      "vendor": "Malibu",
+      "write": 1,
+      "skip": 2,
+      "total": 3
+    },
+    {
+      "vendor": "Sacha Walckhoff",
+      "write": 0,
+      "skip": 2,
+      "total": 2
+    },
+    {
+      "vendor": "Patty Madden",
+      "write": 0,
+      "skip": 2,
+      "total": 2
+    },
+    {
+      "vendor": "Christian Lacroix Europe",
+      "write": 0,
+      "skip": 2,
+      "total": 2
+    },
+    {
+      "vendor": "Fabricut",
+      "write": 0,
+      "skip": 2,
+      "total": 2
+    },
+    {
+      "vendor": "Holland and Sherry",
+      "write": 0,
+      "skip": 1,
+      "total": 1
+    },
+    {
+      "vendor": "Winfield Thybony",
+      "write": 1,
+      "skip": 0,
+      "total": 1
+    },
+    {
+      "vendor": "Steven Abrams Photography",
+      "write": 0,
+      "skip": 1,
+      "total": 1
+    },
+    {
+      "vendor": "Catchii Netherlands Europe",
+      "write": 0,
+      "skip": 1,
+      "total": 1
+    },
+    {
+      "vendor": "Rebecca Moses",
+      "write": 1,
+      "skip": 0,
+      "total": 1
+    },
+    {
+      "vendor": "Pierre Frey",
+      "write": 0,
+      "skip": 1,
+      "total": 1
+    },
+    {
+      "vendor": "Tres Tintas",
+      "write": 1,
+      "skip": 0,
+      "total": 1
+    },
+    {
+      "vendor": "Morris and Company",
+      "write": 0,
+      "skip": 1,
+      "total": 1
+    },
+    {
+      "vendor": "Farrow & Ball",
+      "write": 0,
+      "skip": 1,
+      "total": 1
+    },
+    {
+      "vendor": "Studio Ditte",
+      "write": 0,
+      "skip": 1,
+      "total": 1
+    }
+  ]
+}
\ No newline at end of file
diff --git a/scripts/colorway-title-anchor/out/tk11076-canary-before-after.txt b/scripts/colorway-title-anchor/out/tk11076-canary-before-after.txt
new file mode 100644
index 0000000..fec0301
--- /dev/null
+++ b/scripts/colorway-title-anchor/out/tk11076-canary-before-after.txt
@@ -0,0 +1,217 @@
+TK-11076 COLORWAY CANARY — BEFORE/AFTER (DRY-RUN, no Shopify writes)
+generated 2026-09-01T20:32:04.145Z  |  metafield target: custom.real_color_name
+examined 26  WRITE 23  SKIP 3
+========================================================================================================================
+
+VENDOR: Rebel Walls   FORM: pipe/dash
+  TITLE (untouched)        : Botanical Bliss - Rust | Rebel Walls
+  OLD color-related tags   : [Brown]
+  PARSED colorway          : Rust
+  NEW color: tag           : color:Rust
+  metafield real_color_name: (empty)  ->  Rust
+  PALETTE strays stripped  : [Brown]
+  DECISION                 : WRITE
+
+VENDOR: Clarke And Clarke   FORM: dash-by/dash
+  TITLE (untouched)        : Acanthus - Slate Wp Grey By Clarke And Clarke | Clarke & Clarke William Morris Wallcovering | Botanical & Floral Wallcovering Print
+  OLD color-related tags   : [Pewter]
+  PARSED colorway          : Slate Wp Grey
+  NEW color: tag           : color:Slate Wp Grey
+  metafield real_color_name: (empty)  ->  Slate Wp Grey
+  PALETTE strays stripped  : [Pewter]
+  DECISION                 : WRITE
+
+VENDOR: Clarke And Clarke   FORM: dash-by/dash
+  TITLE (untouched)        : Kruger - Flame  By Clarke And Clarke | Animalia By Emma J Shipley For C&C | Animal/Insects Wallcovering Print
+  OLD color-related tags   : [Orange]
+  PARSED colorway          : Flame
+  NEW color: tag           : color:Flame
+  metafield real_color_name: (empty)  ->  Flame
+  PALETTE strays stripped  : [Orange]
+  DECISION                 : WRITE
+
+VENDOR: Brunschwig & Fils   FORM: dash-by/dash
+  TITLE (untouched)        : Bibliotheque - Greige Multi By Brunschwig & Fils | Volume 56 | Figurative Wallcovering Print
+  OLD color-related tags   : [White]
+  PARSED colorway          : Greige Multi
+  NEW color: tag           : color:Greige Multi
+  metafield real_color_name: (empty)  ->  Greige Multi
+  PALETTE strays stripped  : [White]
+  DECISION                 : WRITE
+
+VENDOR: Brunschwig & Fils   FORM: dash-by/dash
+  TITLE (untouched)        : Treillage Sidewall - Silver By Brunschwig & Fils | Volume 56 Print
+  OLD color-related tags   : [Gray, Light Gray, White]
+  PARSED colorway          : Silver
+  NEW color: tag           : color:Silver
+  metafield real_color_name: Beige  ->  Silver
+  PALETTE strays stripped  : [Gray, Light Gray, White]
+  DECISION                 : WRITE
+
+VENDOR: Brunschwig & Fils   FORM: dash-by/dash
+  TITLE (untouched)        : Wood - Birch Grey By Brunschwig & Fils | Volume 56 |Solid Texture Wallcovering Print
+  OLD color-related tags   : [Light Gray, Oatmeal]
+  PARSED colorway          : Birch Grey
+  NEW color: tag           : color:Birch Grey
+  metafield real_color_name: (empty)  ->  Birch Grey
+  PALETTE strays stripped  : [Light Gray, Oatmeal]
+  DECISION                 : WRITE
+
+VENDOR: Lee Jofa   FORM: pipe/dash
+  TITLE (untouched)        : Ellis Paper - Bayside Wallcovering | Lee Jofa
+  OLD color-related tags   : []
+  PARSED colorway          : Bayside
+  NEW color: tag           : color:Bayside
+  metafield real_color_name: (empty)  ->  Bayside
+  PALETTE strays stripped  : []
+  DECISION                 : WRITE
+
+VENDOR: GP & J Baker   FORM: dash-by/dash
+  TITLE (untouched)        : Ramayana - Emerald By G P & J Baker | Signature Ii Wallcoverings |Toile  Wallcovering Print
+  OLD color-related tags   : [Pale Beige]
+  PARSED colorway          : Emerald
+  NEW color: tag           : color:Emerald
+  metafield real_color_name: (empty)  ->  Emerald
+  PALETTE strays stripped  : [Pale Beige]
+  DECISION                 : WRITE
+
+VENDOR: Groundworks   FORM: pipe/dash
+  TITLE (untouched)        : Channels Paper - Ebony/Iv Wallcovering | Phillipe Romano
+  OLD color-related tags   : [White]
+  PARSED colorway          : Ebony/Iv
+  NEW color: tag           : color:Ebony/Iv
+  metafield real_color_name: (empty)  ->  Ebony/Iv
+  PALETTE strays stripped  : [White]
+  DECISION                 : WRITE
+
+VENDOR: Cole & Son   FORM: pipe/dash
+  TITLE (untouched)        : Matrinah - Old Olive Green | Cole & Son
+  OLD color-related tags   : [color:OLD OLIVE, Sage, Taupe]
+  PARSED colorway          : Old Olive Green
+  NEW color: tag           : color:Old Olive Green
+  metafield real_color_name: OLD OLIVE  ->  Old Olive Green
+  PALETTE strays stripped  : [Sage, Taupe]
+  DECISION                 : WRITE
+
+VENDOR: Cole & Son   FORM: pipe/dash
+  TITLE (untouched)        : Thistle - Lilac/Cerise/White Multi | Cole & Son
+  OLD color-related tags   : [Lilac/Cerise/White, Linen, Taupe]
+  PARSED colorway          : Lilac/Cerise/White Multi
+  NEW color: tag           : color:Lilac/Cerise/White Multi
+  metafield real_color_name: LILAC/CERISE/WHITE  ->  Lilac/Cerise/White Multi
+  PALETTE strays stripped  : [Lilac/Cerise/White, Linen, Taupe]
+  DECISION                 : WRITE
+
+VENDOR: Cole & Son   FORM: pipe/dash
+  TITLE (untouched)        : Savuti - Dark Petrol Multi | Cole & Son
+  OLD color-related tags   : [color:DARK PETROL MULTI, Taupe]
+  PARSED colorway          : Dark Petrol Multi
+  NEW color: tag           : color:Dark Petrol Multi
+  metafield real_color_name: DARK PETROL MULTI  ->  Dark Petrol Multi
+  PALETTE strays stripped  : [Taupe]
+  DECISION                 : WRITE
+
+VENDOR: Rebel Walls   FORM: pipe/dash
+  TITLE (untouched)        : Winding Spring - Black | Rebel Walls
+  OLD color-related tags   : [Charcoal]
+  PARSED colorway          : Black
+  NEW color: tag           : color:Black
+  metafield real_color_name: (empty)  ->  Black
+  PALETTE strays stripped  : [Charcoal]
+  DECISION                 : WRITE
+
+VENDOR: Rebel Walls   FORM: pipe/dash
+  TITLE (untouched)        : Leo - King of the Circus | Rebel Walls
+  OLD color-related tags   : [Silver, White]
+  PARSED colorway          : King Of The Circus
+  NEW color: tag           : color:King Of The Circus
+  metafield real_color_name: (empty)  ->  King Of The Circus
+  PALETTE strays stripped  : [Silver, White]
+  DECISION                 : WRITE
+
+VENDOR: Rebel Walls   FORM: pipe/dash
+  TITLE (untouched)        : Lily Pond - Rainbow | Rebel Walls
+  OLD color-related tags   : [Sage, White]
+  PARSED colorway          : Rainbow
+  NEW color: tag           : color:Rainbow
+  metafield real_color_name: (empty)  ->  Rainbow
+  PALETTE strays stripped  : [Sage, White]
+  DECISION                 : WRITE
+
+VENDOR: Maya Romanoff   FORM: pipe/dash
+  TITLE (untouched)        : Grand Scale Ajiro Chevron - Bamboo | Maya Romanoff
+  OLD color-related tags   : [Beige, Sand]
+  PARSED colorway          : Bamboo
+  NEW color: tag           : color:Bamboo
+  metafield real_color_name: Bamboo  ->  Bamboo
+  PALETTE strays stripped  : [Beige, Sand]
+  DECISION                 : WRITE
+
+VENDOR: Maya Romanoff   FORM: pipe/dash
+  TITLE (untouched)        : Conduit - Tempered Tan | Maya Romanoff
+  OLD color-related tags   : [Oatmeal]
+  PARSED colorway          : Tempered Tan
+  NEW color: tag           : color:Tempered Tan
+  metafield real_color_name: (empty)  ->  Tempered Tan
+  PALETTE strays stripped  : [Oatmeal]
+  DECISION                 : WRITE
+
+VENDOR: Mind the Gap   FORM: pipe/dash
+  TITLE (untouched)        : Obscure - Beige Wallcovering | Mind the Gap
+  OLD color-related tags   : [Black, Light Gray]
+  PARSED colorway          : Beige
+  NEW color: tag           : color:Beige
+  metafield real_color_name: (empty)  ->  Beige
+  PALETTE strays stripped  : [Black, Light Gray]
+  DECISION                 : WRITE
+
+VENDOR: Mind the Gap   FORM: pipe/dash
+  TITLE (untouched)        : Floral Painting - Beige Wallcovering | Mind the Gap
+  OLD color-related tags   : [Black]
+  PARSED colorway          : Beige
+  NEW color: tag           : color:Beige
+  metafield real_color_name: (empty)  ->  Beige
+  PALETTE strays stripped  : [Black]
+  DECISION                 : WRITE
+
+VENDOR: Romo   FORM: pipe/dash
+  TITLE (untouched)        : Raya Silk Embossed - Caper Natural Wallcovering | Romo
+  OLD color-related tags   : [Taupe]
+  PARSED colorway          : Caper Natural
+  NEW color: tag           : color:Caper Natural
+  metafield real_color_name: Caper  ->  Caper Natural
+  PALETTE strays stripped  : [Taupe]
+  DECISION                 : WRITE
+
+VENDOR: Romo   FORM: pipe/dash
+  TITLE (untouched)        : Monazite - Cocktail Wallcovering | Romo
+  OLD color-related tags   : [Taupe]
+  PARSED colorway          : Cocktail
+  NEW color: tag           : color:Cocktail
+  metafield real_color_name: Cocktail  ->  Cocktail
+  PALETTE strays stripped  : [Taupe]
+  DECISION                 : WRITE
+
+VENDOR: Phillip Jeffries   FORM: bare/dash
+  TITLE (untouched)        : Star Dust Silk - Supernova White
+  OLD color-related tags   : []
+  PARSED colorway          : Supernova White
+  NEW color: tag           : color:Supernova White
+  metafield real_color_name: (empty)  ->  Supernova White
+  PALETTE strays stripped  : []
+  DECISION                 : WRITE
+
+VENDOR: Phillip Jeffries   FORM: bare/dash
+  TITLE (untouched)        : Cabana Weave - Thundercloud
+  OLD color-related tags   : [White]
+  PARSED colorway          : Thundercloud
+  NEW color: tag           : color:Thundercloud
+  metafield real_color_name: (empty)  ->  Thundercloud
+  PALETTE strays stripped  : [White]
+  DECISION                 : WRITE
+
+------------------------------------------------------------
+SKIPS (confidence gate — left AS-IS):
+  SKIP  [Rebel Walls]  Arlon Teal | Rebel Walls   reason=no-delimiter (pipe/no-delim)
+  SKIP  [Rebel Walls]  Patinated Diamonds | Rebel Walls   reason=no-delimiter (pipe/no-delim)
+  SKIP  [Rebel Walls]  Memory Lane | Rebel Walls   reason=no-delimiter (pipe/no-delim)
diff --git a/scripts/colorway-title-anchor/out/tk11076-canary-mixed-diff.json b/scripts/colorway-title-anchor/out/tk11076-canary-mixed-diff.json
new file mode 100644
index 0000000..92894c7
--- /dev/null
+++ b/scripts/colorway-title-anchor/out/tk11076-canary-mixed-diff.json
@@ -0,0 +1,612 @@
+{
+  "ts": "2026-09-01T20:32:04.145Z",
+  "mode": "DRY-RUN",
+  "scope": "canary-mixed",
+  "live_products_examined": 26,
+  "would_write": 23,
+  "would_skip": 3,
+  "metafield_target": "custom.real_color_name",
+  "before_after": [
+    {
+      "handle": "botanical-bliss-rust-wallcoverings-dwrw-360566",
+      "vendor": "Rebel Walls",
+      "title": "Botanical Bliss - Rust | Rebel Walls",
+      "form": "pipe/dash",
+      "old_color_related_tags": [
+        "Brown"
+      ],
+      "parsed_colorway": "Rust",
+      "new_color_tag": "color:Rust",
+      "old_metafield_real_color_name": "(empty)",
+      "new_metafield_real_color_name": "Rust",
+      "palette_strays_stripped": [
+        "Brown"
+      ],
+      "wrong_facets_stripped": [],
+      "tags_added": [
+        "color:Rust"
+      ],
+      "tags_removed": [
+        "Brown"
+      ],
+      "decision": "WRITE"
+    },
+    {
+      "handle": "dwkk-127974",
+      "vendor": "Clarke And Clarke",
+      "title": "Acanthus - Slate Wp Grey By Clarke And Clarke | Clarke & Clarke William Morris Wallcovering | Botanical & Floral Wallcovering Print",
+      "form": "dash-by/dash",
+      "old_color_related_tags": [
+        "Pewter"
+      ],
+      "parsed_colorway": "Slate Wp Grey",
+      "new_color_tag": "color:Slate Wp Grey",
+      "old_metafield_real_color_name": "(empty)",
+      "new_metafield_real_color_name": "Slate Wp Grey",
+      "palette_strays_stripped": [
+        "Pewter"
+      ],
+      "wrong_facets_stripped": [],
+      "tags_added": [
+        "color:Slate Wp Grey"
+      ],
+      "tags_removed": [
+        "Pewter"
+      ],
+      "decision": "WRITE"
+    },
+    {
+      "handle": "dwkk-127734",
+      "vendor": "Clarke And Clarke",
+      "title": "Kruger - Flame  By Clarke And Clarke | Animalia By Emma J Shipley For C&C | Animal/Insects Wallcovering Print",
+      "form": "dash-by/dash",
+      "old_color_related_tags": [
+        "Orange"
+      ],
+      "parsed_colorway": "Flame",
+      "new_color_tag": "color:Flame",
+      "old_metafield_real_color_name": "(empty)",
+      "new_metafield_real_color_name": "Flame",
+      "palette_strays_stripped": [
+        "Orange"
+      ],
+      "wrong_facets_stripped": [],
+      "tags_added": [
+        "color:Flame"
+      ],
+      "tags_removed": [
+        "Orange"
+      ],
+      "decision": "WRITE"
+    },
+    {
+      "handle": "dwkk-101530",
+      "vendor": "Brunschwig & Fils",
+      "title": "Bibliotheque - Greige Multi By Brunschwig & Fils | Volume 56 | Figurative Wallcovering Print",
+      "form": "dash-by/dash",
+      "old_color_related_tags": [
+        "White"
+      ],
+      "parsed_colorway": "Greige Multi",
+      "new_color_tag": "color:Greige Multi",
+      "old_metafield_real_color_name": "(empty)",
+      "new_metafield_real_color_name": "Greige Multi",
+      "palette_strays_stripped": [
+        "White"
+      ],
+      "wrong_facets_stripped": [],
+      "tags_added": [
+        "color:Greige Multi"
+      ],
+      "tags_removed": [
+        "White"
+      ],
+      "decision": "WRITE"
+    },
+    {
+      "handle": "dwkk-102027",
+      "vendor": "Brunschwig & Fils",
+      "title": "Treillage Sidewall - Silver By Brunschwig & Fils | Volume 56 Print",
+      "form": "dash-by/dash",
+      "old_color_related_tags": [
+        "Gray",
+        "Light Gray",
+        "White"
+      ],
+      "parsed_colorway": "Silver",
+      "new_color_tag": "color:Silver",
+      "old_metafield_real_color_name": "Beige",
+      "new_metafield_real_color_name": "Silver",
+      "palette_strays_stripped": [
+        "Gray",
+        "Light Gray",
+        "White"
+      ],
+      "wrong_facets_stripped": [],
+      "tags_added": [
+        "color:Silver"
+      ],
+      "tags_removed": [
+        "Gray",
+        "Light Gray",
+        "White"
+      ],
+      "decision": "WRITE"
+    },
+    {
+      "handle": "dwkk-102011",
+      "vendor": "Brunschwig & Fils",
+      "title": "Wood - Birch Grey By Brunschwig & Fils | Volume 56 |Solid Texture Wallcovering Print",
+      "form": "dash-by/dash",
+      "old_color_related_tags": [
+        "Light Gray",
+        "Oatmeal"
+      ],
+      "parsed_colorway": "Birch Grey",
+      "new_color_tag": "color:Birch Grey",
+      "old_metafield_real_color_name": "(empty)",
+      "new_metafield_real_color_name": "Birch Grey",
+      "palette_strays_stripped": [
+        "Light Gray",
+        "Oatmeal"
+      ],
+      "wrong_facets_stripped": [],
+      "tags_added": [
+        "color:Birch Grey"
+      ],
+      "tags_removed": [
+        "Light Gray",
+        "Oatmeal"
+      ],
+      "decision": "WRITE"
+    },
+    {
+      "handle": "ellis-paper-bayside-wallcovering-lee-jofa",
+      "vendor": "Lee Jofa",
+      "title": "Ellis Paper - Bayside Wallcovering | Lee Jofa",
+      "form": "pipe/dash",
+      "old_color_related_tags": [],
+      "parsed_colorway": "Bayside",
+      "new_color_tag": "color:Bayside",
+      "old_metafield_real_color_name": "(empty)",
+      "new_metafield_real_color_name": "Bayside",
+      "palette_strays_stripped": [],
+      "wrong_facets_stripped": [],
+      "tags_added": [
+        "color:Bayside"
+      ],
+      "tags_removed": [],
+      "decision": "WRITE"
+    },
+    {
+      "handle": "dwkk-137742",
+      "vendor": "GP & J Baker",
+      "title": "Ramayana - Emerald By G P & J Baker | Signature Ii Wallcoverings |Toile  Wallcovering Print",
+      "form": "dash-by/dash",
+      "old_color_related_tags": [
+        "Pale Beige"
+      ],
+      "parsed_colorway": "Emerald",
+      "new_color_tag": "color:Emerald",
+      "old_metafield_real_color_name": "(empty)",
+      "new_metafield_real_color_name": "Emerald",
+      "palette_strays_stripped": [
+        "Pale Beige"
+      ],
+      "wrong_facets_stripped": [],
+      "tags_added": [
+        "color:Emerald"
+      ],
+      "tags_removed": [
+        "Pale Beige"
+      ],
+      "decision": "WRITE"
+    },
+    {
+      "handle": "channels-paper-ebony-iv-wallcovering-phillipe-romano-1",
+      "vendor": "Groundworks",
+      "title": "Channels Paper - Ebony/Iv Wallcovering | Phillipe Romano",
+      "form": "pipe/dash",
+      "old_color_related_tags": [
+        "White"
+      ],
+      "parsed_colorway": "Ebony/Iv",
+      "new_color_tag": "color:Ebony/Iv",
+      "old_metafield_real_color_name": "(empty)",
+      "new_metafield_real_color_name": "Ebony/Iv",
+      "palette_strays_stripped": [
+        "White"
+      ],
+      "wrong_facets_stripped": [],
+      "tags_added": [
+        "color:Ebony/Iv"
+      ],
+      "tags_removed": [
+        "White"
+      ],
+      "decision": "WRITE"
+    },
+    {
+      "handle": "dwkk-gc2080a23",
+      "vendor": "Cole & Son",
+      "title": "Matrinah - Old Olive Green | Cole & Son",
+      "form": "pipe/dash",
+      "old_color_related_tags": [
+        "color:OLD OLIVE",
+        "Sage",
+        "Taupe"
+      ],
+      "parsed_colorway": "Old Olive Green",
+      "new_color_tag": "color:Old Olive Green",
+      "old_metafield_real_color_name": "OLD OLIVE",
+      "new_metafield_real_color_name": "Old Olive Green",
+      "palette_strays_stripped": [
+        "Sage",
+        "Taupe"
+      ],
+      "wrong_facets_stripped": [
+        "color:OLD OLIVE"
+      ],
+      "tags_added": [
+        "color:Old Olive Green"
+      ],
+      "tags_removed": [
+        "color:OLD OLIVE",
+        "Sage",
+        "Taupe"
+      ],
+      "decision": "WRITE"
+    },
+    {
+      "handle": "dwkk-gbeb55b32",
+      "vendor": "Cole & Son",
+      "title": "Thistle - Lilac/Cerise/White Multi | Cole & Son",
+      "form": "pipe/dash",
+      "old_color_related_tags": [
+        "Lilac/Cerise/White",
+        "Linen",
+        "Taupe"
+      ],
+      "parsed_colorway": "Lilac/Cerise/White Multi",
+      "new_color_tag": "color:Lilac/Cerise/White Multi",
+      "old_metafield_real_color_name": "LILAC/CERISE/WHITE",
+      "new_metafield_real_color_name": "Lilac/Cerise/White Multi",
+      "palette_strays_stripped": [
+        "Lilac/Cerise/White",
+        "Linen",
+        "Taupe"
+      ],
+      "wrong_facets_stripped": [],
+      "tags_added": [
+        "color:Lilac/Cerise/White Multi"
+      ],
+      "tags_removed": [
+        "Lilac/Cerise/White",
+        "Linen",
+        "Taupe"
+      ],
+      "decision": "WRITE"
+    },
+    {
+      "handle": "dwkk-ge2422bc2",
+      "vendor": "Cole & Son",
+      "title": "Savuti - Dark Petrol Multi | Cole & Son",
+      "form": "pipe/dash",
+      "old_color_related_tags": [
+        "color:DARK PETROL MULTI",
+        "Taupe"
+      ],
+      "parsed_colorway": "Dark Petrol Multi",
+      "new_color_tag": "color:Dark Petrol Multi",
+      "old_metafield_real_color_name": "DARK PETROL MULTI",
+      "new_metafield_real_color_name": "Dark Petrol Multi",
+      "palette_strays_stripped": [
+        "Taupe"
+      ],
+      "wrong_facets_stripped": [
+        "color:DARK PETROL MULTI"
+      ],
+      "tags_added": [
+        "color:Dark Petrol Multi"
+      ],
+      "tags_removed": [
+        "color:DARK PETROL MULTI",
+        "Taupe"
+      ],
+      "decision": "WRITE"
+    },
+    {
+      "handle": "dwrw-74510",
+      "vendor": "Rebel Walls",
+      "title": "Winding Spring - Black | Rebel Walls",
+      "form": "pipe/dash",
+      "old_color_related_tags": [
+        "Charcoal"
+      ],
+      "parsed_colorway": "Black",
+      "new_color_tag": "color:Black",
+      "old_metafield_real_color_name": "(empty)",
+      "new_metafield_real_color_name": "Black",
+      "palette_strays_stripped": [
+        "Charcoal"
+      ],
+      "wrong_facets_stripped": [],
+      "tags_added": [
+        "color:Black"
+      ],
+      "tags_removed": [
+        "Charcoal"
+      ],
+      "decision": "WRITE"
+    },
+    {
+      "handle": "dwrw-75543",
+      "vendor": "Rebel Walls",
+      "title": "Leo - King of the Circus | Rebel Walls",
+      "form": "pipe/dash",
+      "old_color_related_tags": [
+        "Silver",
+        "White"
+      ],
+      "parsed_colorway": "King Of The Circus",
+      "new_color_tag": "color:King Of The Circus",
+      "old_metafield_real_color_name": "(empty)",
+      "new_metafield_real_color_name": "King Of The Circus",
+      "palette_strays_stripped": [
+        "Silver",
+        "White"
+      ],
+      "wrong_facets_stripped": [],
+      "tags_added": [
+        "color:King Of The Circus"
+      ],
+      "tags_removed": [
+        "Silver",
+        "White"
+      ],
+      "decision": "WRITE"
+    },
+    {
+      "handle": "dwrw-74468",
+      "vendor": "Rebel Walls",
+      "title": "Lily Pond - Rainbow | Rebel Walls",
+      "form": "pipe/dash",
+      "old_color_related_tags": [
+        "Sage",
+        "White"
+      ],
+      "parsed_colorway": "Rainbow",
+      "new_color_tag": "color:Rainbow",
+      "old_metafield_real_color_name": "(empty)",
+      "new_metafield_real_color_name": "Rainbow",
+      "palette_strays_stripped": [
+        "Sage",
+        "White"
+      ],
+      "wrong_facets_stripped": [],
+      "tags_added": [
+        "color:Rainbow"
+      ],
+      "tags_removed": [
+        "Sage",
+        "White"
+      ],
+      "decision": "WRITE"
+    },
+    {
+      "handle": "ac2-4x14-maya-50037",
+      "vendor": "Maya Romanoff",
+      "title": "Grand Scale Ajiro Chevron - Bamboo | Maya Romanoff",
+      "form": "pipe/dash",
+      "old_color_related_tags": [
+        "Beige",
+        "Sand"
+      ],
+      "parsed_colorway": "Bamboo",
+      "new_color_tag": "color:Bamboo",
+      "old_metafield_real_color_name": "Bamboo",
+      "new_metafield_real_color_name": "Bamboo",
+      "palette_strays_stripped": [
+        "Beige",
+        "Sand"
+      ],
+      "wrong_facets_stripped": [],
+      "tags_added": [
+        "color:Bamboo"
+      ],
+      "tags_removed": [
+        "Beige",
+        "Sand"
+      ],
+      "decision": "WRITE"
+    },
+    {
+      "handle": "w-78-1389-s-maya-50234",
+      "vendor": "Maya Romanoff",
+      "title": "Conduit - Tempered Tan | Maya Romanoff",
+      "form": "pipe/dash",
+      "old_color_related_tags": [
+        "Oatmeal"
+      ],
+      "parsed_colorway": "Tempered Tan",
+      "new_color_tag": "color:Tempered Tan",
+      "old_metafield_real_color_name": "(empty)",
+      "new_metafield_real_color_name": "Tempered Tan",
+      "palette_strays_stripped": [
+        "Oatmeal"
+      ],
+      "wrong_facets_stripped": [],
+      "tags_added": [
+        "color:Tempered Tan"
+      ],
+      "tags_removed": [
+        "Oatmeal"
+      ],
+      "decision": "WRITE"
+    },
+    {
+      "handle": "obscure",
+      "vendor": "Mind the Gap",
+      "title": "Obscure - Beige Wallcovering | Mind the Gap",
+      "form": "pipe/dash",
+      "old_color_related_tags": [
+        "Black",
+        "Light Gray"
+      ],
+      "parsed_colorway": "Beige",
+      "new_color_tag": "color:Beige",
+      "old_metafield_real_color_name": "(empty)",
+      "new_metafield_real_color_name": "Beige",
+      "palette_strays_stripped": [
+        "Black",
+        "Light Gray"
+      ],
+      "wrong_facets_stripped": [],
+      "tags_added": [
+        "color:Beige"
+      ],
+      "tags_removed": [
+        "Black",
+        "Light Gray"
+      ],
+      "decision": "WRITE"
+    },
+    {
+      "handle": "floral-painting",
+      "vendor": "Mind the Gap",
+      "title": "Floral Painting - Beige Wallcovering | Mind the Gap",
+      "form": "pipe/dash",
+      "old_color_related_tags": [
+        "Black"
+      ],
+      "parsed_colorway": "Beige",
+      "new_color_tag": "color:Beige",
+      "old_metafield_real_color_name": "(empty)",
+      "new_metafield_real_color_name": "Beige",
+      "palette_strays_stripped": [
+        "Black"
+      ],
+      "wrong_facets_stripped": [],
+      "tags_added": [
+        "color:Beige"
+      ],
+      "tags_removed": [
+        "Black"
+      ],
+      "decision": "WRITE"
+    },
+    {
+      "handle": "raya-silk-embossed-caper-romo",
+      "vendor": "Romo",
+      "title": "Raya Silk Embossed - Caper Natural Wallcovering | Romo",
+      "form": "pipe/dash",
+      "old_color_related_tags": [
+        "Taupe"
+      ],
+      "parsed_colorway": "Caper Natural",
+      "new_color_tag": "color:Caper Natural",
+      "old_metafield_real_color_name": "Caper",
+      "new_metafield_real_color_name": "Caper Natural",
+      "palette_strays_stripped": [
+        "Taupe"
+      ],
+      "wrong_facets_stripped": [],
+      "tags_added": [
+        "color:Caper Natural"
+      ],
+      "tags_removed": [
+        "Taupe"
+      ],
+      "decision": "WRITE"
+    },
+    {
+      "handle": "monazite-cocktail-romo",
+      "vendor": "Romo",
+      "title": "Monazite - Cocktail Wallcovering | Romo",
+      "form": "pipe/dash",
+      "old_color_related_tags": [
+        "Taupe"
+      ],
+      "parsed_colorway": "Cocktail",
+      "new_color_tag": "color:Cocktail",
+      "old_metafield_real_color_name": "Cocktail",
+      "new_metafield_real_color_name": "Cocktail",
+      "palette_strays_stripped": [
+        "Taupe"
+      ],
+      "wrong_facets_stripped": [],
+      "tags_added": [
+        "color:Cocktail"
+      ],
+      "tags_removed": [
+        "Taupe"
+      ],
+      "decision": "WRITE"
+    },
+    {
+      "handle": "dwjp-10789-philip-jeffries-star-dust-silk-supernova-white-3224",
+      "vendor": "Phillip Jeffries",
+      "title": "Star Dust Silk - Supernova White",
+      "form": "bare/dash",
+      "old_color_related_tags": [],
+      "parsed_colorway": "Supernova White",
+      "new_color_tag": "color:Supernova White",
+      "old_metafield_real_color_name": "(empty)",
+      "new_metafield_real_color_name": "Supernova White",
+      "palette_strays_stripped": [],
+      "wrong_facets_stripped": [],
+      "tags_added": [
+        "color:Supernova White"
+      ],
+      "tags_removed": [],
+      "decision": "WRITE"
+    },
+    {
+      "handle": "dwjp-10726-philip-jeffries-cabana-weave-thundercloud-3108",
+      "vendor": "Phillip Jeffries",
+      "title": "Cabana Weave - Thundercloud",
+      "form": "bare/dash",
+      "old_color_related_tags": [
+        "White"
+      ],
+      "parsed_colorway": "Thundercloud",
+      "new_color_tag": "color:Thundercloud",
+      "old_metafield_real_color_name": "(empty)",
+      "new_metafield_real_color_name": "Thundercloud",
+      "palette_strays_stripped": [
+        "White"
+      ],
+      "wrong_facets_stripped": [],
+      "tags_added": [
+        "color:Thundercloud"
+      ],
+      "tags_removed": [
+        "White"
+      ],
+      "decision": "WRITE"
+    }
+  ],
+  "skips": [
+    {
+      "handle": "dwrw-76567",
+      "vendor": "Rebel Walls",
+      "title": "Arlon Teal | Rebel Walls",
+      "reason": "no-delimiter",
+      "form": "pipe/no-delim"
+    },
+    {
+      "handle": "dwrw-74241",
+      "vendor": "Rebel Walls",
+      "title": "Patinated Diamonds | Rebel Walls",
+      "reason": "no-delimiter",
+      "form": "pipe/no-delim"
+    },
+    {
+      "handle": "dwrw-76194",
+      "vendor": "Rebel Walls",
+      "title": "Memory Lane | Rebel Walls",
+      "reason": "no-delimiter",
+      "form": "pipe/no-delim"
+    }
+  ]
+}
\ No newline at end of file
diff --git a/scripts/colorway-title-anchor/out/tk11076-nonchromatic-tails.json b/scripts/colorway-title-anchor/out/tk11076-nonchromatic-tails.json
new file mode 100644
index 0000000..99b2f0f
--- /dev/null
+++ b/scripts/colorway-title-anchor/out/tk11076-nonchromatic-tails.json
@@ -0,0 +1,32 @@
+{
+  "write": 9155,
+  "chromatic": 3070,
+  "nonchromatic": 6085,
+  "examples": [
+    "Phillipe Romano: Distressed - Vineyard Polyurethane | Phillipe Romano  => Vineyard Polyurethane",
+    "Phillipe Romano: Elevate - Hula Tekloom | Phillipe Romano  => Hula Tekloom",
+    "Phillipe Romano: Elevate - Rust Tekloom | Phillipe Romano  => Rust Tekloom",
+    "Rebel Walls: Happy Hills, Vanilla | Rebel Walls  => Vanilla",
+    "Phillipe Romano: Mozart - Ink Blackout Fabric | Phillipe Romano  => Ink Blackout Fabric",
+    "Phillipe Romano: Muse - Ridge Silicone Hybrid | Phillipe Romano Wallcoverings  => Ridge Silicone Hybrid",
+    "Surface Stick: Elegante with Surface Stick - Faux Fine Wood Self Adhesive  => Faux Fine Wood Self Adhesive",
+    "Designer Wallcoverings: Lawson Bay Lattice Trellis - Spice Orange  => Spice Orange",
+    "Phillipe Romano: Pinpoint - Clay Vegan Leather | Phillipe Romano  => Clay Vegan Leather",
+    "Phillipe Romano: Pinpoint - Merlot Vegan Leather | Phillipe Romano  => Merlot Vegan Leather",
+    "Phillipe Romano: Pinpoint - Parsley Vegan Leather | Phillipe Romano  => Parsley Vegan Leather",
+    "Phillipe Romano: Pinpoint - Perth Vegan Leather | Phillipe Romano  => Perth Vegan Leather",
+    "Phillipe Romano: Pinpoint - Sea Green Vegan Leather | Phillipe Romano  => Sea Green Vegan Leather",
+    "Phillipe Romano: Pinpoint - Sunset Vegan Leather | Phillipe Romano  => Sunset Vegan Leather",
+    "Phillipe Romano: Polido - Hunter Vegan Leather | Phillipe Romano  => Hunter Vegan Leather",
+    "Phillipe Romano: Polido - Smoke Vegan Leather | Phillipe Romano  => Smoke Vegan Leather",
+    "Phillipe Romano: Polido - Trail Vegan Leather | Phillipe Romano  => Trail Vegan Leather",
+    "Phillipe Romano: Prado - Bark Vegan Leather | Phillipe Romano  => Bark Vegan Leather",
+    "Phillipe Romano: Prado - Black Vegan Leather | Phillipe Romano  => Black Vegan Leather",
+    "Phillipe Romano: Prado - Husk Vegan Leather | Phillipe Romano  => Husk Vegan Leather",
+    "Phillipe Romano: Prado - Oxblood Vegan Leather | Phillipe Romano  => Oxblood Vegan Leather",
+    "Phillipe Romano: Prado - Walnut Vegan Leather | Phillipe Romano  => Walnut Vegan Leather",
+    "Phillipe Romano: Prairie - Marine Breathable Vegan Leather | Phillipe Romano  => Marine Breathable Vegan Leather",
+    "Phillipe Romano: Prime - Aubergiine Silicone Hybrid | Phillipe Romano  => Aubergiine Silicone Hybrid",
+    "Phillipe Romano: Prime - Bark Silicone Hybrid | Phillipe Romano  => Bark Silicone Hybrid"
+  ]
+}
\ No newline at end of file
diff --git a/scripts/colorway-title-anchor/parse-colorway.mjs b/scripts/colorway-title-anchor/parse-colorway.mjs
new file mode 100644
index 0000000..01cbee5
--- /dev/null
+++ b/scripts/colorway-title-anchor/parse-colorway.mjs
@@ -0,0 +1,173 @@
+#!/usr/bin/env node
+/**
+ * TK-11076 — REUSABLE manufacturer-colorway parser (title-anchored, fleet-wide).
+ *
+ * Steve's LOCKED spec: TITLES ARE UNTOUCHED. We READ the live product title and parse the
+ * manufacturer COLORWAY out of it. The colorway is written to a structured metafield and a
+ * color:<colorway> tag; only bare AI-palette color-noise tags are stripped. Where the colorway
+ * cannot be cleanly isolated (or the pattern token itself is a color word) we SKIP — never guess.
+ *
+ * Two title grammars, both anchored on the SAME colorway delimiter:
+ *   DASH-FORM (Kravet family): "<Pattern> - <Colorway> By <Vendor> [| Collection | Type…]"
+ *       e.g. "Egerton - Indigo By Cole & Son", "Chandigarh - Sapphire Blue By Brunschwig & Fils | Volume 56"
+ *   PIPE-FORM (Cole & Son, Maya Romanoff, Mind the Gap, Romo, …): "<Pattern> - <Colorway> | <Vendor>"
+ *       e.g. "Florencecourt - Charcoal | Cole & Son", "Craze - Styled Sage | Maya Romanoff"
+ *   COMMA-FORM (Rebel Walls, …): "<Pattern>, <Colorway> | <Vendor>"
+ *       e.g. "Botanical Bliss - Rust | Rebel Walls", "Furada, Sage | Rebel Walls"
+ *
+ * ALGORITHM (deterministic, no AI):
+ *   1. Strip the VENDOR tail: prefer " By <Vendor>" (case-insensitive) if present; else drop
+ *      everything from the FIRST top-level " | " onward (that segment is Vendor / Collection / Type).
+ *   2. From the remaining PATTERN+COLORWAY left-side, isolate the colorway as the text after the
+ *      LAST colorway delimiter — " - " (or unicode dashes) OR ", " — whichever occurs last.
+ *      (Using the LAST delimiter safely handles pattern names that contain an earlier hyphen/comma.)
+ *   3. Clean the colorway: drop a trailing "Wallcovering(s)"/"Wallpaper(s)" noise word, trim.
+ *   4. CONFIDENCE GATE → return {colorway:null, skip:true, reason} when:
+ *        - no colorway delimiter present (pattern & colorway run together, e.g. "Aquila Rust") — cannot
+ *          isolate without guessing;  reason='no-delimiter'
+ *        - the isolated colorway is empty / a placeholder ("Colorway 03") / absurdly long;  reason='empty'|'placeholder'|'too-long'
+ *        - the isolated colorway equals the whole left-side (delimiter was the vendor separator only);  reason='no-pattern-split'
+ *        - the PATTERN side is EMPTY (delimiter at very start);  reason='no-pattern'
+ *   NOTE: per Steve, we KEEP the mfr's literal colorway verbatim (incl. doubled words like "Green Green",
+ *   slash-joined "Buff/Gold", multi-word "Sapphire Blue") — we do NOT bucket to a generic hue.
+ *
+ * The colorway may itself CONTAIN a color word (that is the whole point — "Indigo", "Sapphire Blue").
+ * Rule-6's "pattern token itself contains a color word → SKIP" only bites the no-delimiter case, which
+ * we already SKIP: without a delimiter we can't tell whether a color word belongs to the pattern
+ * ("Blue Willow") or is the colorway, so we never fabricate a split.
+ */
+
+const DASHES = /\s+[-–—]\s+/;               // " - ", " – ", " — "
+const VENDOR_BY = /\s+By\s+.+$/i;           // " By <Vendor> …" (Kravet family)
+const decode = s => String(s || '')
+  .replace(/&amp;/g, '&').replace(/&#38;/g, '&').replace(/&#39;/g, "'")
+  .replace(/&quot;/g, '"').replace(/&#34;/g, '"').replace(/&nbsp;/g, ' ');
+const collapse = s => decode(s).replace(/\s+/g, ' ').trim();
+// Unicode-aware Title Case: uppercase the first letter of each whitespace/slash/&/,/- run,
+// WITHOUT touching intra-word accented letters (fixes "Andalucía" -> was "AndalucíA").
+export const titleCase = s => collapse(s).replace(/(^|[\s/&,\-–—])([\p{L}])/gu, (_, sep, ch) => sep + ch.toUpperCase());
+
+const PLACEHOLDER = /^colorway\b/i;
+// Trailing marketing/noise phrases that are NEVER part of a colorway. Stripped from the FULL left-side
+// BEFORE the pattern|colorway split so "Ranger - Flax Vegan Leather - New Colors" splits at "- Flax…".
+const TRAILING_MARKETING = /\s*[-–—]\s*(new colors?|new colorways?|new|limited stock|limited edition|discontinued)\s*$/i;
+const TRAILING_NOISE = /\s+(wall\s*coverings?|wall\s*papers?)\s*$/i;
+
+// MATERIAL / SUBSTRATE suffixes the DW title format glues onto the colorway
+// (e.g. "Clay Vegan Leather", "Ink Blackout Fabric", "Marine Breathable Vegan Leather").
+// The real mfr colorway is the leading token(s); these trailing material words are NOT the colorway.
+// Ordered longest-first; matched only as a trailing suffix, and only if a non-empty colorway remains.
+const MATERIAL_SUFFIX = new RegExp(
+  '\\s+(' + [
+    'breathable vegan leather', 'vegan leather', 'silicone hybrid', 'blackout fabric',
+    'self adhesive', 'faux fine wood', 'faux wood grain', 'polyurethane', 'tekloom',
+    'graffiti-free vinyl', 'graffiti free vinyl', 'grasscloth', 'durable vinyl', 'silk',
+    'mylar', 'cork', 'sisal', 'raffia', 'jute', 'fabric', 'vinyl', 'wallcovering', 'wallpaper',
+  ].join('|') + ')\\s*$', 'i');
+
+/**
+ * Parse the manufacturer colorway from a live product title.
+ * @param {string} title  the LIVE Shopify product title (never modified)
+ * @param {string} [vendor]  the product's vendor (optional; used only to strip a trailing " | <Vendor>")
+ * @returns {{colorway: string|null, form: string, skip: boolean, reason: string|null, pattern: string|null}}
+ */
+export function parseColorway(title, vendor) {
+  const raw = collapse(title);
+  if (!raw) return { colorway: null, form: 'none', skip: true, reason: 'empty-title', pattern: null };
+
+  // 1. Strip vendor tail.
+  let form, left;
+  if (VENDOR_BY.test(raw)) {
+    form = 'dash-by';
+    left = raw.replace(VENDOR_BY, '').trim();          // "<Pattern> - <Colorway>"
+  } else if (raw.includes(' | ')) {
+    form = 'pipe';
+    left = raw.split(' | ')[0].trim();                  // first top-level segment = "<Pattern> - <Colorway>"
+  } else {
+    form = 'bare';
+    left = raw.trim();                                  // no vendor delimiter at all
+  }
+  left = left.replace(TRAILING_NOISE, '').trim();
+  left = left.replace(TRAILING_MARKETING, '').trim();   // drop "- New Colors" etc. before the split
+
+  if (!left) return { colorway: null, form, skip: true, reason: 'empty-after-vendor-strip', pattern: null };
+
+  // 2. Split PATTERN | COLORWAY.
+  //    DASH is the authoritative mfr delimiter → prefer it. Use the LAST " - " so a hyphenated
+  //    pattern name splits correctly, and keep EVERYTHING after it (colorways can contain commas,
+  //    e.g. "Black, Yellow, Red, Burgundy" is one multi-color colorway — do NOT split on those commas).
+  //    Only when there is NO dash do we treat a COMMA as the delimiter (Rebel Walls "Pattern, Colorway"),
+  //    and then take everything after the FIRST comma so a multi-color comma-list stays whole.
+  let idx = -1, delimLen = 0, delim = null;
+  const dashMatches = [...left.matchAll(/\s+[-–—]\s+/g)];
+  if (dashMatches.length) {
+    const m = dashMatches[dashMatches.length - 1];
+    idx = m.index; delimLen = m[0].length; delim = 'dash';
+  } else {
+    const m = left.match(/,\s+/);              // FIRST comma
+    if (m) { idx = m.index; delimLen = m[0].length; delim = 'comma'; }
+  }
+
+  if (idx < 0) {
+    // No delimiter → pattern & colorway are fused (e.g. "Aquila Rust"). Cannot isolate → SKIP.
+    return { colorway: null, form: form + '/no-delim', skip: true, reason: 'no-delimiter', pattern: left };
+  }
+
+  const pattern = left.slice(0, idx).trim();
+  let colorway = left.slice(idx + delimLen).trim().replace(TRAILING_NOISE, '').trim();
+  // Strip a trailing material/substrate suffix ONLY if a non-empty colorway remains
+  // ("Clay Vegan Leather" -> "Clay"; but "Vegan Leather" alone would strip to '' -> keep as-is).
+  const stripped = colorway.replace(MATERIAL_SUFFIX, '').trim();
+  if (stripped) colorway = stripped;
+  form = form + '/' + delim;
+
+  // 3/4. Confidence gate.
+  if (!pattern) return { colorway: null, form, skip: true, reason: 'no-pattern', pattern: null };
+  if (!colorway) return { colorway: null, form, skip: true, reason: 'empty', pattern };
+  if (PLACEHOLDER.test(colorway)) return { colorway: null, form, skip: true, reason: 'placeholder', pattern };
+  if (colorway.length > 60) return { colorway: null, form, skip: true, reason: 'too-long', pattern };
+
+  return { colorway: titleCase(colorway), form, skip: false, reason: null, pattern: titleCase(pattern) };
+}
+
+// -------- self-test (node parse-colorway.mjs --selftest) --------
+if (process.argv[1] && process.argv[1].endsWith('parse-colorway.mjs') && process.argv.includes('--selftest')) {
+  const cases = [
+    ['Egerton - Indigo By Cole & Son', 'Cole & Son', 'Indigo', false],
+    ['Chandigarh - Sapphire Blue By Brunschwig & Fils | Volume 56 Print', 'Brunschwig & Fils', 'Sapphire Blue', false],
+    ['Faux Grass Texture On Vinyl - Sandstone Beige By Brunschwig & Fils | T', 'Brunschwig & Fils', 'Sandstone Beige', false],
+    ['Bird And Thistle - Green Green By Brunschwig & Fils | Toile', 'Brunschwig & Fils', 'Green Green', false],
+    ['Woods & Stars - Buff/Gold | Cole & Son', 'Cole & Son', 'Buff/Gold', false],
+    ['Florencecourt - Charcoal | Cole & Son', 'Cole & Son', 'Charcoal', false],
+    ['Craze - Styled Sage | Maya Romanoff', 'Maya Romanoff', 'Styled Sage', false],
+    ['Ajiro Fanfare Silver Birch Luster - Beige Wallcovering | Maya Romanoff', 'Maya Romanoff', 'Beige', false],
+    ['Botanical Bliss - Rust | Rebel Walls', 'Rebel Walls', 'Rust', false],
+    ['Furada, Sage | Rebel Walls', 'Rebel Walls', 'Sage', false],
+    ['Fish Shoal, Night Wallcoverings | Rebel Walls', 'Rebel Walls', 'Night', false],
+    ['Zebra Skin, Candy | Rebel Walls', 'Rebel Walls', 'Candy', false],
+    ['Aquila Rust | Rebel Walls', 'Rebel Walls', null, true],            // no-delimiter → SKIP
+    ['Pom Pom | Rebel Walls', 'Rebel Walls', null, true],                // no colorway → SKIP
+    ['Whimsy Wallcovering', 'Traditional Wh', null, true],               // bare, no delim → SKIP
+    ['Grasscloth Natural | Phillipe Romano', 'Phillipe Romano', null, true], // no delim, fused → SKIP
+    ['TightWiki Grasscloth Deep Wedgewood Blue | Phillipe Romano', 'Phillipe Romano', null, true], // no delim → SKIP
+    ['Rayure Lione - Ciel Light Blue By Brunschwig & Fils | Madeleine Castaing', 'Brunschwig & Fils', 'Ciel Light Blue', false],
+    ['Sevilla Weaves - Andalucía Grey', 'Phillip Jeffries', 'Andalucía Grey', false],  // accent preserved
+    ['Aristocracy - Black, Yellow, Red, Burgundy Wallcovering | Mind the Gap', 'Mind the Gap', 'Black, Yellow, Red, Burgundy', false], // dash split, comma-list kept whole
+    ['Furada, Sage | Rebel Walls', 'Rebel Walls', 'Sage', false],                       // comma-form single
+    ['Pinpoint - Clay Vegan Leather | Phillipe Romano', 'Phillipe Romano', 'Clay', false],       // material suffix stripped
+    ['Prado - Oxblood Vegan Leather | Phillipe Romano', 'Phillipe Romano', 'Oxblood', false],
+    ['Mozart - Ink Blackout Fabric | Phillipe Romano', 'Phillipe Romano', 'Ink', false],
+    ['Distressed - Vineyard Polyurethane | Phillipe Romano', 'Phillipe Romano', 'Vineyard', false],
+    ['Umaji - Ivory By Brunschwig & Fils | Solid Wallcovering Grasscloth', 'Brunschwig & Fils', 'Ivory', false], // suffix only in the vendor tail, not colorway
+    ['Ranger - Flax Vegan Leather - New Colors | Phillipe Romano', 'Phillipe Romano', 'Flax', false],  // "- New Colors" marketing tail dropped
+    ['Regent - Pepper Graffiti-Free Vinyl | Phillipe Romano', 'Phillipe Romano', 'Pepper', false],
+  ];
+  let pass = 0, fail = 0;
+  for (const [t, v, expCw, expSkip] of cases) {
+    const r = parseColorway(t, v);
+    const ok = (r.skip === expSkip) && ((r.colorway || null) === (expCw || null));
+    if (ok) pass++; else { fail++; console.log(`FAIL: ${JSON.stringify(t)}\n  got  colorway=${JSON.stringify(r.colorway)} skip=${r.skip} reason=${r.reason} form=${r.form}\n  want colorway=${JSON.stringify(expCw)} skip=${expSkip}`); }
+  }
+  console.log(`\nself-test: ${pass}/${pass + fail} passed`);
+  process.exit(fail ? 1 : 0);
+}
diff --git a/scripts/colorway-title-anchor/tk11076-colorway-retag.mjs b/scripts/colorway-title-anchor/tk11076-colorway-retag.mjs
new file mode 100644
index 0000000..b2d9bb3
--- /dev/null
+++ b/scripts/colorway-title-anchor/tk11076-colorway-retag.mjs
@@ -0,0 +1,304 @@
+#!/usr/bin/env node
+/**
+ * TK-11076 — FLEET colorway-correction driver (title-anchored). Steve's LOCKED spec.
+ *
+ * For each affected LIVE product (status ACTIVE, carrying AI-Analyzed-v2):
+ *   1. TITLE UNTOUCHED — parse the mfr colorway from the live title (parse-colorway.mjs, both grammars).
+ *   2. Write the parsed colorway to metafield  custom.real_color_name (single_line_text_field) —
+ *      the established store field for the real (non-AI) mfr colorway. NOT invented; NOT an AI field.
+ *   3. Set a  color:<mfr colorway>  tag (the REAL mfr colorway value, verbatim — not a hue bucket).
+ *   4. STRIP only the wrong AI palette-noise tags: (a) any existing  color:*  facet (AI-guessed,
+ *      rebuilt from the title anchor); (b) bare color-word tags that are in THIS product's OWN
+ *      AI palette (custom.color_details[].name + custom.color_{1,2,3}_name + custom.background_color +
+ *      custom.color) AND are NOT part of the real colorway. Source-field driven, per-product —
+ *      NEVER a global color denylist. PRESERVE every non-color tag and the real colorway.
+ *   5. CONFIDENCE GATE: parseColorway returns skip=true → leave the product AS-IS (no tag, no metafield,
+ *      no strip). Tracked in the skips report.
+ *   6. NEVER change variant SKUs, price, status, sales channels, or images. Hue buckets OUT OF SCOPE.
+ *
+ * PostgreSQL-BEFORE-Shopify: the colorway anchor lives in the TITLE (already on Shopify) so there is
+ * no staging-table write needed; we record the old tags + old metafield in this VENDOR'S OWN rollback
+ * file (never a shared file — a shared file got clobbered by a concurrent race in a prior run), then
+ * write Shopify (authoritative), then per-item verify. Reversible + ledgered.
+ *
+ *   node tk11076-colorway-retag.mjs --vendor="Cole & Son"                 # DRY-RUN report (no writes)
+ *   node tk11076-colorway-retag.mjs --vendor="Cole & Son" --canary=8      # DRY-RUN, print N-product before/after diff
+ *   node tk11076-colorway-retag.mjs --vendor="Cole & Son" --apply         # LIVE (gated — do NOT run this pass)
+ *   node tk11076-colorway-retag.mjs --canary-mixed                        # DRY-RUN diverse cross-vendor canary
+ */
+import fs from 'node:fs';
+import path from 'node:path';
+import { fileURLToPath } from 'node:url';
+import { execSync } from 'node:child_process';
+import { parseColorway, titleCase } from './parse-colorway.mjs';
+
+const HERE = path.dirname(fileURLToPath(import.meta.url));
+const OUT = path.join(HERE, 'out');
+fs.mkdirSync(OUT, { recursive: true });
+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 = process.env.SHOPIFY_API_VERSION || '2024-10';
+const APPLY = process.argv.includes('--apply');
+const arg = k => (process.argv.find(a => a.startsWith(`--${k}=`)) || '').split('=').slice(1).join('=');
+const VENDOR = arg('vendor');
+const CANARY = parseInt(arg('canary') || '0', 10);
+const CANARY_MIXED = process.argv.includes('--canary-mixed');
+const CW_META_NS = 'custom', CW_META_KEY = 'real_color_name';
+const sleep = ms => new Promise(r => setTimeout(r, ms));
+const decode = s => String(s || '').replace(/&amp;/g, '&').replace(/&#38;/g, '&').replace(/&#39;/g, "'").replace(/&quot;/g, '"');
+const norm = s => decode(s).toLowerCase().replace(/\s*&\s*/g, ' & ').replace(/\s*\/\s*/g, '/').replace(/\s+/g, ' ').trim();
+
+// Color lexicon — GUARD ONLY. A tag qualifies as a palette-noise stray candidate only if every
+// space/slash/&-joined token is a known color word/modifier. The STRIP DECISION is palette membership
+// (this product's OWN AI palette), not this list. This just prevents mistaking a non-color tag.
+const COLOR_MODIFIERS = new Set(['soft','pale','dark','light','deep','bright','warm','cool','dusty',
+  'metallic','metalic','antique','burnished','muted','rich','pastel','vintage','&','and','on','multi']);
+const COLOR_WORDS = new Set([
+  'white','black','grey','gray','ivory','cream','beige','stone','dove','taupe','greige','oatmeal',
+  'alabaster','bone','eggshell','linen','sand','almond','pearl','chalk','parchment','champagne','ecru',
+  'silver','gold','gilver','bronze','copper','pewter','platinum','mica','graphite','charcoal','slate','pebble',
+  'teal','aqua','turquoise','jade','celadon','sage','mint','emerald','forest','olive','chartreuse','lime','green',
+  'blue','navy','indigo','cobalt','sapphire','cornflower','sky','denim','powder','peacock','azure','petrol','cyan',
+  'red','crimson','scarlet','burgundy','wine','russet','terracotta','rust','brick','coral','salmon','blush','cerise',
+  'pink','rose','fuchsia','magenta','lavender','lilac','purple','plum','amethyst','mauve','violet','orchid',
+  'yellow','ochre','saffron','mustard','honey','amber','peach','apricot','tan','walnut','espresso','coffee','caramel',
+  'brown','chocolate','khaki','ecru','natural','neutral','midnight','seafoam','ash','buff','fawn','sepia','clay',
+  'orange','tangerine','rust','marigold','flax','wheat','straw','camel','mahogany','cocoa','mocha','stone','smoke']);
+function isColorish(tag) {
+  const t = norm(tag);
+  if (!t) return false;
+  return t.split(/[\s/&]+/).filter(Boolean).every(p => COLOR_WORDS.has(p) || COLOR_MODIFIERS.has(p));
+}
+
+if (!TOKEN) { console.error('FATAL: set SHOPIFY_ADMIN_TOKEN'); process.exit(1); }
+
+// Query handles safely (SQL via temp file — no shell interpolation of vendor names / $$).
+function psqlHandles(vendor, extraWhere, limit) {
+  const sql = `SELECT handle FROM shopify_products
+    WHERE vendor = '${vendor.replace(/'/g, "''")}' AND status='ACTIVE'
+      AND tags ILIKE '%AI-Analyzed-v2%' AND ${extraWhere}
+    ORDER BY random() LIMIT ${parseInt(limit, 10)}`;
+  const tmp = path.join(OUT, `.q-${Date.now()}-${Math.random().toString(36).slice(2)}.sql`);
+  fs.writeFileSync(tmp, sql);
+  try { return execSync(`psql "${DB}" -tAf "${tmp}"`, { encoding: 'utf8' }).trim().split('\n').map(s => s.trim()).filter(Boolean); }
+  finally { fs.unlinkSync(tmp); }
+}
+
+async function gql(query, variables) {
+  for (let a = 0; a < 6; a++) {
+    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, variables }), signal: AbortSignal.timeout(45000) }).catch(e => ({ _err: e }));
+    if (res._err) { await sleep(1500 * (a + 1)); continue; }
+    const j = await res.json().catch(() => ({}));
+    if (j.errors) { if (a === 5) return { errors: j.errors }; await sleep(1500 * (a + 1)); continue; }
+    await sleep(350); return j.data;
+  }
+  return { errors: [{ message: 'gql retries exhausted' }] };
+}
+
+const PROD_FIELDS = `id handle status title vendor tags
+  cw:  metafield(namespace:"${CW_META_NS}", key:"${CW_META_KEY}"){ value }
+  cd:  metafield(namespace:"custom", key:"color_details"){ value }
+  c1:  metafield(namespace:"custom", key:"color_1_name"){ value }
+  c2:  metafield(namespace:"custom", key:"color_2_name"){ value }
+  c3:  metafield(namespace:"custom", key:"color_3_name"){ value }
+  cc:  metafield(namespace:"custom", key:"color"){ value }
+  bg:  metafield(namespace:"custom", key:"background_color"){ value }
+  variants(first:3){ nodes{ sku } }`;
+
+async function fetchVendor(vendor) {
+  const out = []; let cursor = null;
+  const q = `query($cursor:String){ products(first:100, query:${JSON.stringify(`vendor:'${vendor}' AND tag:AI-Analyzed-v2 AND status:active`)}, after:$cursor){
+      pageInfo{ hasNextPage endCursor } nodes{ ${PROD_FIELDS} } } }`;
+  for (let g = 0; g < 80; g++) {
+    const d = await gql(q, { cursor });
+    if (!d || d.errors) { console.error('fetch err', JSON.stringify(d && d.errors)); break; }
+    out.push(...d.products.nodes);
+    if (!d.products.pageInfo.hasNextPage) break;
+    cursor = d.products.pageInfo.endCursor;
+  }
+  return out;
+}
+
+async function fetchHandles(handles) {
+  const out = [];
+  for (const h of handles) {
+    const d = await gql(`query($q:String!){ products(first:1, query:$q){ nodes{ ${PROD_FIELDS} } } }`, { q: `handle:${h}` });
+    if (d && d.products && d.products.nodes[0]) out.push(d.products.nodes[0]);
+  }
+  return out;
+}
+
+// Build the per-product plan (pure — no writes). Returns {plan|null, skip|null}.
+function planProduct(p) {
+  const parsed = parseColorway(p.title, p.vendor);
+  if (parsed.skip) {
+    return { skip: { handle: p.handle, vendor: p.vendor, title: p.title, reason: parsed.reason, form: parsed.form } };
+  }
+  const colorway = parsed.colorway;                       // real mfr colorway, verbatim (title case)
+  const facet = 'color:' + colorway;
+
+  // THIS product's OWN AI palette (source-field driven, per-product).
+  const palette = new Set();
+  try { for (const c of JSON.parse((p.cd && p.cd.value) || '[]')) { const n = c && (c.name || c.color); if (n) palette.add(norm(n)); } } catch {}
+  for (const v of [p.c1 && p.c1.value, p.c2 && p.c2.value, p.c3 && p.c3.value, p.bg && p.bg.value, p.cc && p.cc.value]) if (v) palette.add(norm(v));
+
+  const colorwayTokens = new Set(norm(colorway).split(/[\s/&,]+/).filter(Boolean));
+  const oldTags = Array.isArray(p.tags) ? p.tags.slice() : String(p.tags || '').split(',').map(s => s.trim()).filter(Boolean);
+
+  const newTags = [];
+  const strippedFacet = [], strippedPalette = [];
+  for (const t of oldTags) {
+    if (/^color:/i.test(t)) { strippedFacet.push(t); continue; }               // rebuild from title
+    // strip a bare AI-palette color word that is NOT part of the real colorway
+    const isPaletteStray = palette.has(norm(t)) && isColorish(t) && !colorwayTokens.has(norm(t)) && norm(t) !== norm(colorway);
+    if (isPaletteStray) { strippedPalette.push(t); continue; }
+    if (norm(t) === norm(facet)) continue;                                     // dedup, re-added below
+    newTags.push(t);                                                            // PRESERVE all non-color tags
+  }
+  if (!newTags.some(t => norm(t) === norm(facet))) newTags.push(facet);
+
+  const oldCw = (p.cw && p.cw.value) || '';
+  const metafieldChange = norm(oldCw) !== norm(colorway);
+  const tagsChanged = JSON.stringify([...oldTags].map(norm).sort()) !== JSON.stringify([...newTags].map(norm).sort());
+  if (!metafieldChange && !tagsChanged) return { plan: null };  // already correct
+
+  return { plan: {
+    id: p.id, handle: p.handle, status: p.status, vendor: p.vendor, title: p.title,
+    colorway, form: parsed.form, oldTags, newTags, strippedFacet, strippedPalette,
+    oldColorwayMeta: oldCw, newColorwayMeta: colorway,
+  } };
+}
+
+function diffRow(pl) {
+  const add = pl.newTags.filter(t => !pl.oldTags.includes(t));
+  const rem = pl.oldTags.filter(t => !pl.newTags.includes(t));
+  const oldColorRelated = pl.oldTags.filter(t => /^color:/i.test(t) || pl.strippedPalette.includes(t));
+  return {
+    handle: pl.handle, vendor: pl.vendor, title: pl.title, form: pl.form,
+    old_color_related_tags: oldColorRelated,
+    parsed_colorway: pl.colorway,
+    new_color_tag: 'color:' + pl.colorway,
+    old_metafield_real_color_name: pl.oldColorwayMeta || '(empty)',
+    new_metafield_real_color_name: pl.newColorwayMeta,
+    palette_strays_stripped: pl.strippedPalette,
+    wrong_facets_stripped: pl.strippedFacet,
+    tags_added: add, tags_removed: rem,
+    decision: 'WRITE',
+  };
+}
+
+async function run() {
+  let live = [];
+  if (CANARY_MIXED) {
+    // diverse cross-vendor + Steve's named checks (Cole & Son Charcoal, Rebel Walls Rust)
+    const wanted = {
+      'Clarke And Clarke': 2, 'Brunschwig & Fils': 3, 'Lee Jofa': 1, 'GP & J Baker': 1, 'Groundworks': 1,
+      'Cole & Son': 3, 'Rebel Walls': 3, 'Maya Romanoff': 2, 'Mind the Gap': 2, 'Romo': 2, 'Phillip Jeffries': 2,
+    };
+    // pinned handles Steve called out (Cole & Son Florencecourt-Charcoal, Rebel Walls Botanical Bliss - Rust)
+    const pinned = ['botanical-bliss-rust-wallcoverings-dwrw-360566'];
+    const pin = await fetchHandles(pinned);
+    live.push(...pin);
+    for (const [v, n] of Object.entries(wanted)) {
+      const rows = psqlHandles(v, "title LIKE '% - %'", n * 3);
+      const got = await fetchHandles(rows.slice(0, n * 3));
+      // keep the first n that produce a WRITE (non-skip) so the canary is informative, but also keep 1 skip if available
+      const writes = got.filter(p => !planProduct(p).skip).slice(0, n);
+      live.push(...writes);
+    }
+    // add a couple of deliberate SKIP cases to show the gate working
+    const skipRows = psqlHandles('Rebel Walls', "title NOT LIKE '% - %' AND title NOT LIKE '%,%'", 3);
+    live.push(...await fetchHandles(skipRows));
+  } else if (VENDOR) {
+    live = await fetchVendor(VENDOR);
+    if (CANARY) live = live.slice(0, CANARY * 4); // oversample; diff picks first CANARY writes below
+  } else {
+    console.error('specify --vendor="X" or --canary-mixed'); process.exit(1);
+  }
+
+  const plans = [], skips = [], diffRows = [];
+  for (const p of live) {
+    const r = planProduct(p);
+    if (r.skip) { skips.push(r.skip); continue; }
+    if (r.plan) { plans.push(r.plan); diffRows.push(diffRow(r.plan)); }
+  }
+
+  // canary diff cap
+  const shownDiff = CANARY ? diffRows.slice(0, CANARY) : diffRows;
+  const shownSkips = skips;
+
+  const report = {
+    ts: new Date().toISOString(),
+    mode: APPLY ? 'APPLY' : 'DRY-RUN',
+    scope: CANARY_MIXED ? 'canary-mixed' : VENDOR,
+    live_products_examined: live.length,
+    would_write: plans.length,
+    would_skip: skips.length,
+    metafield_target: `${CW_META_NS}.${CW_META_KEY}`,
+    before_after: shownDiff,
+    skips: shownSkips,
+  };
+  const outFile = path.join(OUT, CANARY_MIXED ? 'tk11076-canary-mixed-diff.json' : `tk11076-${(VENDOR || 'x').replace(/\W+/g, '_')}-diff.json`);
+  fs.writeFileSync(outFile, JSON.stringify(report, null, 2));
+
+  // console table
+  console.log(`\n=== TK-11076 colorway retag ${report.mode} · scope=${report.scope} ===`);
+  console.log(`examined ${live.length} · WRITE ${plans.length} · SKIP ${skips.length} · metafield=${report.metafield_target}`);
+  for (const d of shownDiff) {
+    console.log(`\n[${d.vendor}] "${d.title}"  (${d.form})`);
+    console.log(`   old color-tags : [${d.old_color_related_tags.join(', ')}]`);
+    console.log(`   parsed colorway: ${d.parsed_colorway}`);
+    console.log(`   new color: tag : ${d.new_color_tag}`);
+    console.log(`   metafield real_color_name: ${d.old_metafield_real_color_name}  ->  ${d.new_metafield_real_color_name}`);
+    console.log(`   palette strays stripped  : [${d.palette_strays_stripped.join(', ')}]`);
+    console.log(`   DECISION: WRITE`);
+  }
+  if (shownSkips.length) {
+    console.log(`\n--- SKIPS (confidence gate) ---`);
+    for (const s of shownSkips) console.log(`   SKIP [${s.vendor}] "${s.title}"  reason=${s.reason} (${s.form})`);
+  }
+  console.log(`\nfull diff JSON: ${outFile}`);
+
+  if (!APPLY) { console.log(`\nDRY-RUN — no Shopify or PG writes. (LIVE fleet write is gated; do not run --apply this pass.)`); return; }
+
+  // ---- LIVE APPLY (per-vendor rollback file; NOT reached this canary pass) ----
+  if (!VENDOR) { console.error('refuse --apply without a single --vendor (per-vendor rollback isolation)'); process.exit(1); }
+  const ROLLBACK = path.join(process.env.HOME, `.claude/yolo-queue/executed-reversible/TK-11076-${VENDOR.replace(/\W+/g, '_')}-rollback.json`);
+  const rollback = fs.existsSync(ROLLBACK) ? JSON.parse(fs.readFileSync(ROLLBACK, 'utf8')) : {};
+  let done = 0, pass = 0, fail = 0, batch = 0;
+  for (const pl of plans) {
+    rollback[pl.handle] = { id: pl.id, ts: new Date().toISOString(), old_tags: pl.oldTags, new_tags: pl.newTags,
+      old_metafield: pl.oldColorwayMeta, new_metafield: pl.newColorwayMeta };
+    fs.writeFileSync(ROLLBACK, JSON.stringify(rollback, null, 2));
+
+    const d = await gql(`mutation($input:ProductInput!){ productUpdate(input:$input){ product{ id }
+        metafields:product{ id } userErrors{ field message } } }`,
+      { input: { id: pl.id, tags: pl.newTags,
+        metafields: [{ namespace: CW_META_NS, key: CW_META_KEY, type: 'single_line_text_field', value: pl.newColorwayMeta }] } });
+    const ue = d && d.productUpdate && d.productUpdate.userErrors;
+    if (ue && ue.length) { console.error(`   userErrors ${pl.handle}: ${JSON.stringify(ue)}`); fail++; continue; }
+
+    const v = await gql(`query($id:ID!){ product(id:$id){ tags m:metafield(namespace:"${CW_META_NS}",key:"${CW_META_KEY}"){ value } } }`, { id: pl.id });
+    const lt = (v && v.product && v.product.tags) || [];
+    const facetOk = lt.some(t => norm(t) === norm('color:' + pl.colorway));
+    const metaOk = norm((v && v.product && v.product.m && v.product.m.value) || '') === norm(pl.colorway);
+    const ok = facetOk && metaOk;
+    ok ? pass++ : (fail++, console.error(`   VERIFY FAIL ${pl.handle}: facetOk=${facetOk} metaOk=${metaOk}`));
+    done++; console.log(`[${done}/${plans.length}] ${pl.handle} ${ok ? '✓' : 'VERIFY-FAIL'}`);
+
+    try { execSync(`node "${path.join(process.env.HOME, '.claude/yolo-queue/executed-reversible/log-exec.mjs')}" ` +
+      `--agent vp-dw-commerce --ticket TK-11076 --action "colorway retag ${pl.handle}: color:${pl.colorway}" --blast 1 ` +
+      `--undo "cd ${HERE} && node tk11076-rollback.mjs --vendor=\\"${VENDOR}\\" --only=${pl.handle} --apply" ` +
+      `--verify "GET ${pl.id} — real_color_name + color: facet == title colorway; palette strays gone; title/price/status/sku/image unchanged"`,
+      { stdio: 'ignore' }); } catch {}
+
+    batch++;
+    if (batch % 25 === 0 && done < plans.length) { console.log('   …90s inter-batch gap…'); await sleep(90000); }
+    else await sleep(700);
+  }
+  console.log(`\nDONE ${VENDOR}: ${done} · verify-pass ${pass} · verify-fail ${fail} · rollback=${ROLLBACK}`);
+}
+run();
diff --git a/scripts/colorway-title-anchor/tk11076-rollback.mjs b/scripts/colorway-title-anchor/tk11076-rollback.mjs
new file mode 100644
index 0000000..e2d531b
--- /dev/null
+++ b/scripts/colorway-title-anchor/tk11076-rollback.mjs
@@ -0,0 +1,33 @@
+#!/usr/bin/env node
+/**
+ * TK-11076 rollback — restore old tags + old custom.real_color_name from a VENDOR'S OWN rollback file.
+ * Never touches titles/price/status/sku/image (the forward run never did either).
+ *   node tk11076-rollback.mjs --vendor="Cole & Son"                 # DRY-RUN (list what would revert)
+ *   node tk11076-rollback.mjs --vendor="Cole & Son" --apply         # revert ALL for that vendor
+ *   node tk11076-rollback.mjs --vendor="Cole & Son" --only=h1,h2 --apply
+ */
+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 arg = k => (process.argv.find(a => a.startsWith(`--${k}=`)) || '').split('=').slice(1).join('=');
+const VENDOR = arg('vendor');
+const ONLY = (arg('only') || '').split(',').filter(Boolean);
+if (!TOKEN || !VENDOR) { console.error('need SHOPIFY_ADMIN_TOKEN + --vendor="X"'); process.exit(1); }
+const ROLLBACK = path.join(process.env.HOME, `.claude/yolo-queue/executed-reversible/TK-11076-${VENDOR.replace(/\W+/g, '_')}-rollback.json`);
+if (!fs.existsSync(ROLLBACK)) { console.error('no rollback file:', ROLLBACK); process.exit(1); }
+const map = JSON.parse(fs.readFileSync(ROLLBACK, 'utf8'));
+const sleep = ms => new Promise(r => setTimeout(r, ms));
+async function gql(q, v) { const r = 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 r.json()).data; }
+const entries = Object.entries(map).filter(([h]) => !ONLY.length || ONLY.includes(h));
+console.log(`${APPLY ? 'APPLY' : 'DRY-RUN'} rollback ${VENDOR}: ${entries.length} products`);
+for (const [h, r] of entries) {
+  console.log(`  ${h}: tags -> [${(r.old_tags || []).join(', ')}]  real_color_name -> "${r.old_metafield || '(empty)'}"`);
+  if (!APPLY) continue;
+  await gql(`mutation($i:ProductInput!){ productUpdate(input:$i){ userErrors{ message } } }`,
+    { i: { id: r.id, tags: r.old_tags || [], metafields: [{ namespace: 'custom', key: 'real_color_name', type: 'single_line_text_field', value: r.old_metafield || '' }] } });
+  await sleep(500);
+}
+console.log(APPLY ? 'rollback applied.' : 'DRY-RUN — re-run with --apply to revert.');

← 3d0551a auto-data-snapshot: 2026-09-01T12:55:30 (10 data files) — sc  ·  back to Designerwallcoverings  ·  auto-data-snapshot: 2026-09-01T13:36:10 (3 data files) — scr 062be9a →