[object Object]

← back to Ticket System

TK-10002 PLAN v2: bound full stray family (71 products/81 strays), fix guard gid bug, prove acyclic 61-product sweep, complete settlement flags (26)

dda63dbad1952c5d151995818f5d01fc06153cd4 · 2026-07-30 09:49:20 -0700 · Steve Abrams

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

Files touched

Diff

commit dda63dbad1952c5d151995818f5d01fc06153cd4
Author: Steve Abrams <steve@designerwallcoverings.com>
Date:   Thu Jul 30 09:49:20 2026 -0700

    TK-10002 PLAN v2: bound full stray family (71 products/81 strays), fix guard gid bug, prove acyclic 61-product sweep, complete settlement flags (26)
    
    Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---
 tk10002-resku/PLAN.md | 400 +++++++++++++++++++++++---------------------------
 1 file changed, 187 insertions(+), 213 deletions(-)

diff --git a/tk10002-resku/PLAN.md b/tk10002-resku/PLAN.md
index e9005556..1ec57c9d 100644
--- a/tk10002-resku/PLAN.md
+++ b/tk10002-resku/PLAN.md
@@ -1,261 +1,235 @@
-# TK-10002 — Re-SKU Plan (READ-ONLY, ruling-ready)
+# TK-10002 — Re-SKU Plan v2 (READ-ONLY, ruling-ready, red-team-corrected)
+
+# ► TRUE TOTAL IN SCOPE: **105 ACTIVE products** ◄
+**61 confident-STRAY · 10 REVIEW-stray · 32 REVIEW-FULLDUP/empty · 2 ROUTE-to-merge.**
+**Stray family bounded NOW (not deferred): 71 products carry 81 mislabeled variants; 61/71 are a
+mechanical, PROVABLY-ACYCLIC family sweep.**
 
 **Ticket:** TK-10002-dw-duplicate-active-products-hunt-yolo · **Agent:** vp-dw-commerce · **Date:** 2026-07-30
 **Store:** `designer-laboratory-sandbox.myshopify.com` (LIVE prod DW store) · API `2024-10`
-**Guard baseline:** `skuTakenOnActiveProduct` — fail-closed + case-folded UPPER compare over
-`variant_sku`/`sku`/`dw_sku`, base + `base-%` suffix, excluding ARCHIVED (`sku-registry.js`, TK-10002 hardening).
-**Cost:** `$0 (local)` — PG-mirror reads + git + read-only Shopify GraphQL. No metered/paid-model calls.
-**Authorization status:** NOTHING WRITTEN. Plan only. Every write below stays gated for Steve.
-**Contrarian note:** a separate contrarian reviewer will challenge this before it reaches Steve.
+**Cost:** `$0 (local)` — PG-mirror reads + git + read-only Shopify GraphQL (935 product reads, no metered/paid-model calls).
+**Authorization status:** NOTHING WRITTEN. Every write below is drafted and gated for Steve.
+**Supersedes:** PLAN.md v1 (commit 935fe1a), which under-bounded the family at 20 and carried the guard
+self-exclusion bug. v2 fixes all 7 red-team holes; see §0.
 
 ---
 
-## TL;DR (one paragraph)
-
-The current LIVE false-collision set is **23 ACTIVE groups (not 32)** — the 9 MTG/MIS/PRW groups on the
-stale af440a2 list have **self-resolved** (their second members were re-SKU'd or archived since). **All 23
-live groups are `DW Bespoke Studio`.** The bug is NOT a whole-product SKU collision: each of these products
-is a full multi-variant peel/prepasted/vinyl/gold/silver line whose 15 variants already carry the product's
-own correct base — but **one stray variant** ("Type 2 Textured Vinyl / 2ft × 1ft Sample", occasionally a
-peel/vinyl sample) was templated with a *different* product's base, so the fix is to **re-SKU that ONE
-variant** to `<product's own base>-type-2-vinyl-Sample`. Of the 23 groups: **10 are STRAY** (each member's
-stray variant re-SKUs to its own base — a mechanical, deterministic fix), and **12 are REVIEW-FULLDUP**
-(both members legitimately share the *same* base across ALL variants → the `dw_sku` itself is duplicated
-upstream, so which member keeps the base needs SKU-analyst/Steve judgment) — plus **1 group routes to the
-memo-sample merge project** (Cici's Periwinkle). **A hard new finding**: the strays form a **daisy-chain
-across MORE products than the 23** — several correct target SKUs are currently squatted by a *third*
-product's un-fixed stray (e.g. "Classic Book Shelf" mislabels its sample as `Dig-1952-…`), so the STRAY
-fixes must be applied as **ONE coordinated full-family sweep** of every ACTIVE Bespoke product carrying a
-stray `-type-2-vinyl-Sample`, not just these 23 — done that way they mutually unblock; done piecemeal several
-trip the guard. Net: **10 STRAY groups (20 products) have a confident deterministic fix but must sweep as a
-family; 12 REVIEW-FULLDUP groups (25 products) need judgment; 1 group (1 product) routes to the memo-twin
-project.**
-
-**Approve options for Steve:** (A) approve the full coordinated STRAY family sweep (recommended) — but
-authorize enumerating the full live stray family first (beyond these 23); (B) approve STRAY + hold all
-REVIEW-FULLDUP; (C) line-item per group below.
+## SECTION 0 — What the red-team caught and how v2 fixes it
 
----
+| # | Red-team finding | v2 fix + evidence |
+|---|---|---|
+| 1 | **Full stray family not bounded — deferred to apply-time (the killer).** | **BOUNDED NOW.** Scanned all **935** ACTIVE Bespoke products' LIVE variants (`scan-family-v2.mjs`, $0). Result: **71 products carry 81 true foreign-base stray variants** — matches the contrarian's independent ~81, not v1's 20. Full enumerated list in `strays-v3.json`. See §2. |
+| 2 | **Audit ALL sample variants per product, not just type-2-vinyl.** | Done. **10 products carry >1 stray** (e.g. Classic Book Shelf `7664512761907` has BOTH `Dig-3001-25-pre-Sample` — a prefix-stray on its own number — AND `Dig-1952-25-type-2-vinyl-Sample`). Per-variant list in `strays-v3.json → results[].strays[]`. See §2C. |
+| 3 | **Guard self-exclusion bug: bare gid vs `gid://…/NNN`.** | **Fixed + all verdicts recomputed.** Mirror stores `shopify_id='gid://shopify/Product/NNN'`; v1 compared bare `NNN` → self never excluded → false BLOCKED. v2 (`guard-verify-v2.mjs`/`final-verdict.mjs`) normalizes both sides via `bareId()`. Recomputed over the full family: **44 CLEAR today, 26 self-clear on sweep, 0 blocked by an out-of-sweep foreigner.** See §3. |
+| 4 | **Vendor-filter trap.** | Stated exactly. **The Shopify `vendor` field literally = `"DW Bespoke Studio"`** (935 ACTIVE). The string "Architectural Wallcoverings" lives ONLY in the title suffix (`… \| Architectural Wallcoverings`). Enumeration predicate = `upper(status)='ACTIVE' AND vendor='DW Bespoke Studio'` reading LIVE variants (mirror keeps only 1 variant/product, so a mirror-only scan would miss strays — that is why we read all 935 live). See §1. |
+| 5 | **Coordinated-sweep safety: prove acyclic + idempotent + partial-failure.** | **Proven acyclic:** over the 61 confident products, **0 corrected-SKU targets are shared by >1 product** (each writes only its OWN unique base) → no two writes contend the same target → no cycle possible. Idempotent + resumable procedure in §5. |
+| 6 | **Complete settlement flags across ALL in-scope.** | Re-scanned all 105 in-scope titles. **26 settlement-sensitive** products flagged (incl. **Willington 1940's Branches Chinoiserie `7664636100659`** the contrarian named, + the butterfly/bird/tropical/floral set). Full list §4. Fitzwilliam (`7664584851507`) is "1970's Pop Art" — not botanical/animal — correctly NOT flagged. |
+| 7 | **Re-bucket Royal Lady Frames + re-verify maybes/Periwinkle.** | Royal Lady Frames (`7548957392947`, `DIG-320255-mural` on own base `DIG-320256`) **promoted REVIEW→confident-STRAY** (guard-CLEAR, one-variant mechanical fix, acyclic). Rosie's Roses (`DIG-553801`, ids 7664593633331/7664629514291) + Minimalist Simple Leaves (`DIG-2400065`, 7664493002803 vs real owner 7315944046643) confirmed **TRUE-DUP → REVIEW (archive-one candidate)**. Periwinkle twin route call HOLDS (§2D). |
 
-## SECTION 0 — What changed vs the stale af440a2 list (why not 32)
+---
 
-Re-derived live this session (query below). The stale plan's 32 REVIEW groups included MTG-403024/403235/403490/403723,
-MIS-18313/18366W1/18398, PRW-65002/65003/65016-Sample. **None of those collide on ACTIVE anymore:**
-- **MTG × 4, MIS × 3** — each now has exactly **1 ACTIVE** member on that variant_sku (the partner was
-  re-SKU'd/removed since af440a2). Confirmed via mirror.
-- **PRW × 3** — each now has **1 ACTIVE + 1 ARCHIVED** (the memo twin was archived). No ACTIVE collision.
+## SECTION 1 — Scope predicate & the structural bug (exact)
 
-So this pass supersedes the stale list. **Trust only this live set.** Reproduce:
+**Universe scanned (fix #4):**
 ```sql
-with grp as (
-  select variant_sku from shopify_products
-  where upper(status)='ACTIVE' and variant_sku is not null and variant_sku !~* '^null'
-  group by variant_sku having count(*)>1)
-select p.variant_sku, count(*) n,
-  count(distinct lower(regexp_replace(coalesce(dw_sku,''),'[^a-z0-9]','','gi'))) distinct_dwsku,
-  count(distinct lower(regexp_replace(coalesce(title,''),'[^a-z0-9]','','gi'))) distinct_title
-from shopify_products p join grp g using(variant_sku)
-where upper(p.status)='ACTIVE' group by p.variant_sku order by 1;
--- → 23 rows, all vendor='DW Bespoke Studio'
+SELECT shopify_id, title, variant_sku FROM shopify_products
+WHERE upper(status)='ACTIVE' AND vendor='DW Bespoke Studio';   -- 935 products
 ```
+Then each product's ALL live variants were read from Shopify GraphQL (the mirror stores only ONE
+variant SKU per product, so a mirror-only pass is blind to strays on products whose sampled variant
+is a clean roll — hence the 935 live reads).
+
+**The bug (structural).** Each modern Bespoke product = a 16-variant line (Peel-and-Stick / Prepasted /
+Type-2-Vinyl / Type-2-PVC / Gold / Silver × sizes 2x12/2x27/Sample). All 16 variant SKUs should share the
+product's own base (e.g. `Dig-7700743-25-*`). A **templating shift-by-one** left one-or-two variants —
+nearly always the `-type-2-vinyl-Sample` and/or `-pre-Sample` — carrying the **previous** design's number
+(e.g. Cherries Jubilee own base `DIG-1952-25`, but its Type-2-Vinyl-Sample = `Dig-1100-25-type-2-vinyl-Sample`).
+That foreign string is what the mirror sampled and what pairwise-collides. **The product already owns the
+correct `<base>-type-2-vinyl-2x12/-2x27`, it is just missing the matching `-Sample`.** So the fix is a
+per-variant SKU correction, NOT a merge, NOT a whole-product re-SKU.
 
 ---
 
-## SECTION 1 — The bug, exactly (structural)
-
-Each Bespoke product = a multi-variant line: Peel-and-Stick / Prepasted / Traditional / Vinyl / Type-2-Vinyl
-/ Gold / Silver / Non-Pasted × sizes (2×3 … 2×27) + a "…/ 2ft × 1ft Sample" per finish. Live probe of every
-group member shows: **15 of the 16 variants already carry the product's OWN correct base** (e.g. Cherries
-Jubilee = `DIG-1952-25-type-2-vinyl-2x12`, `-2x27`, gold, silver, etc.). Only **one** variant — nearly always
-"Type 2 Textured Vinyl / 2ft × 1ft Sample" — was templated with a **foreign base** (`Dig-1100-25-type-2-vinyl-Sample`).
-That foreign string is what the mirror sampled and what collides. **The stray is purely mislabeled — the product
-already owns the correct `<base>-type-2-vinyl-2x12/-2x27` variants, it's just missing the matching `-Sample`.**
-So the remedy is a per-variant SKU correction, NOT a merge and NOT a whole-product re-SKU.
-
-**Daisy-chain (hard finding, changes execution):** The strays chain. Product A's stray points at B's base;
-B's stray points at C's base; etc. The mirror's pairwise collision query (23 groups) only catches
-byte-identical stray strings — but the guard's base+suffix match reveals the correct target SKU for several
-STRAY members is **currently squatted by a THIRD ACTIVE product's un-fixed stray** (proven live):
-- Target `DIG-1952-25-type-2-vinyl-Sample` (for Cherries Jubilee) is squatted by product **7664512761907
-  "Classic Book Shelf"** (own base `Book-3001-25`) whose sample is mislabeled `Dig-1952-25-type-2-vinyl-Sample`.
-- Target `DIG-171052-25-type-2-vinyl-Sample` (for Charlotte's black&blush) is squatted by **7664490577971
-  "Charlotte's black"** (own base `DIG-171051-25`) whose sample is mislabeled `Dig-171052-…`.
-- Same shape blocks `DIG-171053`, `DIG-2400089`, `DIG-40021`, `DIG-85335`, `DIG-98621`, `Dig-7700743`.
-
-⇒ These fixes **mutually unblock** only when the whole stray family is swept in one coordinated pass. Applied
-piecemeal, ~8 of the STRAY targets trip the fail-closed guard (verified — see Section 3). The full stray
-family is **larger than these 23** (mirror shows 185 ACTIVE `…-type-2-vinyl-Sample` rows; how many are
-mislabeled must be enumerated LIVE, which is the first apply-time step, not something this READ-ONLY scoping
-pass could bound).
+## SECTION 2 — The FULL bounded stray family (71 products / 81 variants)
 
----
+Enumerated live; complete rows in `strays-v3.json`. Split:
 
-## SECTION 2 — Group dispositions (all 23)
-
-Legend: **STRAY** = each member re-SKUs its one stray variant to its own base (deterministic).
-**REVIEW-FULLDUP** = both members share the same base across ALL variants (duplicate dw_sku upstream) → judgment.
-**ROUTE** = belongs to another project, do not mint here.
-
-### 2A. STRAY (10 groups / 20 products) — confident deterministic fix, sweep-as-family
-
-| # | colliding variant_sku | member gid | product (own base) | stray variant → corrected SKU |
-|---|---|---|---|---|
-| 1 | `Dig-1100-25-type-2-vinyl-Sample` | 7664512794675 | Classic Cherries Jubilee (`DIG-1952-25`) | → `DIG-1952-25-type-2-vinyl-Sample` |
-| 1 | | 7664512827443 | Classic green floral vintage (`Vin-1100-25`) | → `Vin-1100-25-type-2-vinyl-Sample` |
-| 2 | `Dig-171053-25-type-2-vinyl-Sample` | 7664490512435 | Charlotte's blush pink (`DIG-171053-25`) | → `DIG-171053-25-type-2-vinyl-Sample` |
-| 2 | | 7664490545203 | Charlotte's black & blush (`DIG-171052-25`) | → `DIG-171052-25-type-2-vinyl-Sample` |
-| 3 | `Dig-2400089-25-type-2-vinyl-Sample` | 7664462233651 | Blushed Marble Mural (`DIG-2400089-25`) | → `DIG-2400089-25-type-2-vinyl-Sample` |
-| 3 | | 7664462364723 | Blush Tide Mural (`DIG-2400085-25`) | → `DIG-2400085-25-type-2-vinyl-Sample` |
-| 4 | `Dig-240407-25-type-2-vinyl-Sample` | 7664493002803 | Minimalist Simple Leaves (`DIG-2400065-25`) | → `DIG-2400065-25-type-2-vinyl-Sample` |
-| 4 | | 7664584884275 | Fairfield Happy Days 1970s (`DIG-74118-25`) | → `DIG-74118-25-type-2-vinyl-Sample` |
-| 5 | `Dig-240416-25-type-2-vinyl-Sample` | 7664512237619 | Boudoir Ladies Pink (`DIG-68000-25`) | → `DIG-68000-25-type-2-vinyl-Sample` |
-| 5 | | 7664512663603 | Cello Damask Sepia Beige (`DIG-98621-25`) | → `DIG-98621-25-type-2-vinyl-Sample` |
-| 6 | `Dig-25-25-type-2-vinyl-Sample` | 7664513646643 | Deep Reef Mural (`DIG_10082002-25`) | → `DIG_10082002-25-type-2-vinyl-Sample` |
-| 6 | | 7664586686515 | Thug Stripe Cream Gun (`DIG-439241 Sample-25`) | → `DIG-439241 Sample-25-type-2-vinyl-Sample` ⚠ own base contains a space+"Sample"; ugly-but-unique, REVIEW-soft |
-| 7 | `Dig-40021-25-type-2-vinyl-Sample` | 7664512073779 | Bottles by Jordan Carlyle (`DIG-40021-25`) | → `DIG-40021-25-type-2-vinyl-Sample` |
-| 7 | | 7664512434227 | Cello Damask Charcoal Grey (`DIG-85335-25`) | → `DIG-85335-25-type-2-vinyl-Sample` |
-| 8 | `Dig-7700743-25-type-2-vinyl-Sample` | 7664488218675 | Cranes in the Cherry Blossom (`Dig-240426-Sample-25`) | → `Dig-240426-Sample-25-type-2-vinyl-Sample` |
-| 8 | | 7664586915891 | Geomino 1970s Geometric (`Dig-7700743-25`) | → `Dig-7700743-25-type-2-vinyl-Sample` |
-| 9 | `Dig-770089-25-type-2-vinyl-Sample` | 7664584851507 | Fitzwilliam 1970s Pop Art (`DIG-740108-25`) | → `DIG-740108-25-type-2-vinyl-Sample` |
-| 9 | | 7664636100659 | Willington 1940s Chinoiserie (`DIG-435086-25`) | → `DIG-435086-25-type-2-vinyl-Sample` |
-
-*(Group 8 members split STRAY: Cranes' stray points at 7700743 [Geomino's base]; the fix re-bases each to its
-own — 240426 and 7700743 respectively. Correct and unambiguous.)*
-
-**Group 10 note (`Dig-240417-25-type-2-vinyl-Sample`, ids 7664489529395 + 7774188404787):** this looked STRAY
-but both members share own base `Dig-240416-Sample25` across all 15 variants → it is actually **FULLDUP**, moved
-to 2B. Also flagged: 7774188404787 "**Nocturnal Anthurium Tropical**" → **settlement gate** (see Section 4).
-
-### 2B. REVIEW-FULLDUP (12 groups / 25 products) — duplicate base upstream, needs judgment. DO NOT mint blindly.
-
-Both members carry the **same** base across ALL variants (or an empty/broken base). Re-SKU must assign a NEW
-unique base (color/name-suffix) to one member — a per-group SKU-analyst/Steve call, not a mechanical fix.
-
-| colliding | members (gid · own base · title) | why REVIEW |
-|---|---|---|
-| `-25-gold-2x27` | 7664591568947 · `-25` · Fiona's 1920's Floral · 7664592289843 · `-25` · Hilly's Hippo 1970's | **EMPTY base** `-25` across every variant on BOTH — needs two brand-new DW SKUs minted from scratch (mfr# unknown) |
-| `AIDIG-4350142-…_Flame_Scarlet…` | 7675395145779 · `AIDIG-4350142-25` · Piermont 1940's Retro · 7675395178547 · same · Piermont 1940's Cody Retro | entire AIDIG variant set shared; one keeps base, other needs color/name suffix |
-| `DIG-2400069-25-type-2-vinyl-2x27` | 7664493068339 · `DIG-2400069-25` · Ethereal Dreamy Clouds · 7664493101107 · same · Hand-Drawn Green Stripes | both carry DIG-2400069-25 across ALL variants |
-| `DIG-2400093` | 7589565300787 · `DIG-2400093` · Playform Mural · 7589588402227 · same · Watercolor Dots Mural | 2-variant roll+sample; both share DIG-2400093 |
-| `Dig-240417-25-type-2-vinyl-Sample` | 7664489529395 · `Dig-240416-Sample25` · Calm Elegance · 7774188404787 · same · Nocturnal Anthurium | both share `Dig-240416-Sample25`; **Anthurium → settlement gate** |
-| `Dig-240420` | 7502400847923 · `Dig-240420` · Golden Peaks Mural · 7502401634355 · same · Lavender Mist Peaks Mural | 2-variant roll+sample; both share Dig-240420 |
-| `DIG-320255-mural` | 7547203026995 · `DIG-320255` · Royal Lady Frames Pop Art · 7548957392947 · `DIG-320256` · …Half Drop | dw_skus already distinct (320255/320256); only the **roll** variant_sku `DIG-320255-mural` is shared → a straightforward per-variant re-SKU, but flagged REVIEW because base already has a `DIG-320255-sample` sibling; low-risk, could promote to STRAY on Steve's OK |
-| `DIG-5531287-25-type-2-vinyl-2x27` | 7664593338419 · `DIG-5531287-25` · Wanda's Wisteria (Mid-Century) · 7664593371187 · same · Wanda's Wisteria (Floral) | both carry DIG-5531287-25; likely genuine near-dup of one design in two crops |
-| `DIG-553801-25-type-2-vinyl-2x27` | 7664593633331 · `DIG-553801-25` · Rosie's Roses (Traditional) · 7664629514291 · same · Rosie's Roses 1950's | both carry DIG-553801-25; **possible TRUE same-design dupe** — SKU-analyst should check whether one should ARCHIVE instead of re-SKU |
-| `DIG-762124-Sample-25-type-2-vinyl-2x27` | 7664598614067 · `DIG-762124-Sample-25` · Gregorious Gulls Coastal · 7664616800307 · same · Zepellin Zebras Yellow | both share DIG-762124-Sample-25; two distinct designs → color-suffix one |
-| `Dig-770064-25-type-2-vinyl-2x27` | 7664638427187 · `Dig-770064-25` · Hanover 1970's Geometric · 7664638459955 · same · Bailey Bottles of Booze | both carry Dig-770064-25 |
-| `DIG-977081-Sample-25-type-2-vinyl-2x27` | 7664592814131 · `Dig-977081-25` · Riley's Rainbow of Retro · 7664592846899 · same · Fremont 1970S Fern | both carry Dig-977081-25 |
-| `DIG-240434-vinyl-2x1-sample` (**n=3**) | 7663707619379 · `DIG-240434` · Rosé Luxe · 7663711354931 · same · Soft Nude Gradient · 7663711682611 · same · Watercolor Dots | **THREE** products all carry `DIG-240434-*`; needs 2 of 3 re-based (color-suffix); dw_sku suffix differs only by case (`-peel-sample` vs `-peel-Sample`) |
-
-### 2C. ROUTE — memo-sample twin (1 group / do NOT mint)
-
-| colliding | members | route |
+### 2A. CONFIDENT-STRAY — 61 products / 68 variants (mechanical, acyclic sweep)
+Each carries 1–2 variants whose base differs from the product's dominant base (by design-number, or by
+prefix on the same number — e.g. `Dig-3001` on a `Book-3001` product). Corrected SKU = swap the stray's
+leading `<prefix>-<num>` for the product's own — every other char (finish/size/case) preserved.
+**All 61 corrected SKUs were run through the FIXED guard.** Verdict per §3. Representative rows (full 68 in
+`final-verdict.json → confident[]`):
+
+| pid | own base | stray → corrected | note |
+|---|---|---|---|
+| 7664512794675 | DIG-1952-25 | `Dig-1100-25-type-2-vinyl-Sample` → `DIG-1952-25-type-2-vinyl-Sample` | Cherries Jubilee |
+| 7664512827443 | Vin-1100-25 | `Dig-1100-25-type-2-vinyl-Sample` → `Vin-1100-25-type-2-vinyl-Sample` + `-pre-Sample` | green floral (2 strays) |
+| 7664512761907 | Book-3001-25 | `Dig-1952-25-type-2-vinyl-Sample` + `Dig-3001-25-pre-Sample` → `Book-3001-25-…` | **Classic Book Shelf — 2 strays (fix #2)** |
+| 7664490545203 | DIG-171052-25 | `Dig-171053-25-type-2-vinyl-Sample` → `DIG-171052-25-…` | Charlotte's black&blush |
+| 7664490577971 | DIG-171051-25 | `Dig-171052-25-type-2-vinyl-Sample` → `DIG-171051-25-…` | Charlotte's black |
+| 7548957392947 | DIG-320256 | `DIG-320255-mural` → `DIG-320256-mural` | **Royal Lady Frames — promoted from REVIEW (fix #7)** |
+| 7664584851507 | DIG-740108-25 | `Dig-770089-25-type-2-vinyl-Sample` → `DIG-740108-25-…` | Fitzwilliam (not settlement) |
+| 7664636100659 | DIG-435086-25 | `Dig-770089-25-type-2-vinyl-Sample` → `DIG-435086-25-…` | **Willington — SETTLEMENT-hold (§4)** |
+| … | … | … | +53 more, all in `final-verdict.json` |
+
+The family forms a **daisy-chain** (A's stray → B's base, B's stray → C's base, …). Confirmed live for the
+whole run, e.g. the `DIG-85330…85337` and `DIG-60014…60018` sequences each shift by one. Because every
+corrected SKU is the product's OWN base and each product already owns the rest of that base's variant set,
+the chain resolves when swept together (see §3, §5).
+
+### 2B. REVIEW-stray — malformed own-base (3 products) — NO mechanical fix
+The product's own base has no clean core boundary, so a safe graft can't be computed → SKU-analyst call:
+| pid | own base | why |
 |---|---|---|
-| `DIG-4350959-Periwinkle - Sample` | **REAL:** 6707947208755 · Cici's Periwinkle Traditional (vendor *Architectural Wallcoverings*, multi/sample, CONTINUE) · **TWIN:** 7801847447603 · "Cici's Periwinkle Wallcovering \| **DW Bespoke Studios**" (single **Default Title** variant, `inventoryPolicy=DENY`, **dw_sku NULL**, created 2026-03-23) | The twin is a memo/null-Sample shadow of the real product — the same pattern the stale plan flagged for PRW. **Route to the deferred null-Sample / memo-sample merge project. Do NOT mint a corrected SKU here — minting orphans the twin.** |
+| 7664490840115 | `DIG-MOD-108SAMPLE-Sample-25` | design-number is literally "25"; `-MOD-` ambiguity; 2 strays |
+| 7664490872883 | `DIG-MOD-107SAMPLE-25` | same MOD ambiguity; 2 strays |
+| 7664512892979 | `DIG-55379425` | no separator before finish token → graft would abut letters/finish |
+
+### 2C. REVIEW-stray — post-sweep residual true-dup (7 products) — NO mechanical fix
+Correcting the stray would target a base another product LEGITIMATELY owns → a real duplicate base, not a
+mislabel. Cannot be swept mechanically; needs an archive-one-or-rebase decision:
+| pid | corrected target | conflicts with (real owner) | disposition |
+|---|---|---|---|
+| 7391684657203 | DIG-24001 | 7062435037235 "Peach Mode Plaid" owns DIG-24001 | rebase or archive-one |
+| 7535350022195 | Dig-352500 | 7664489070643 "Rainbow Electricity" owns Dig-352500 | rebase or archive-one |
+| 7664493002803 | DIG-2400065 | 7315944046643 **2nd "Minimalist Simple Leaves"** owns DIG-2400065 | **TRUE same-design dup — archive-one candidate** |
+| 7664512270387 | DIG-435098 | 7664636002355 "Carmellos" owns DIG-435098 | rebase or archive-one |
+| 7664488284211 / 7664489529395 / 7774188404787 | Dig-240416-25-type-2-vinyl-Sample | **each other** (3 products share own base `Dig-240416-Sample25`) | 3-way FULLDUP; rebase 2 of 3. **7774188404787 = Nocturnal Anthurium → SETTLEMENT-hold** |
+
+**Multi-stray products (fix #2), full set (10):** 7664488251443, 7664490840115, 7664490872883,
+7664511451187, 7664512761907, 7664512827443, 7664512892979, 7664513646643, 7664640917555, 7664640983091 —
+each lists both strays in `strays-v3.json`.
+
+### 2D. Non-stray in-scope partners (34 products) — REVIEW-FULLDUP / empty-base / ROUTE
+These collide pairwise but carry NO foreign base (their variant set is internally consistent). They are the
+CLEAN partner of a stray, a genuine duplicate-base pair, or the memo twin:
+- **~16 clean partners / genuine FULLDUP pairs** — both members share one base across all 16 variants
+  (2400069, 5531287, 553801, 770064, 2400093, 240420, 240434-triple, AIDIG-4350142, 977081, 762124…).
+  Re-SKU must mint a color/name-suffixed NEW base for one member → SKU-analyst/Steve. Two are likely TRUE
+  same-design dupes (`DIG-553801` Rosie's Roses ×2; `DIG-2400065` Minimalist Simple Leaves ×2 via §2C) →
+  archive-one candidates.
+- **Empty-base `-25`** — `7664591568947` Fiona's Floral + `7664592289843` Hilly's Hippo both carry base
+  `-25` across all variants → need two brand-new DW SKUs minted from a real mfr# (unknown → REVIEW).
+- **ROUTE (Periwinkle twin, 2 products):** `6707947208755` Cici's Periwinkle Traditional (REAL, vendor
+  *Architectural Wallcoverings*, CONTINUE) + `7801847447603` "Cici's Periwinkle \| DW Bespoke Studios"
+  (memo/null-Sample TWIN, single Default-Title variant, `inventoryPolicy=DENY`, dw_sku NULL, created
+  2026-03-23). **Route the twin to the null-Sample / memo-sample merge project — do NOT mint here
+  (minting orphans the twin).**
 
 ---
 
-## SECTION 3 — Guard verification of the STRAY corrected SKUs (exact guard logic, in-SQL)
+## SECTION 3 — Guard verification, RECOMPUTED with the fix (fix #3)
 
-Ran each STRAY corrected SKU through the exact `skuTakenOnActiveProduct` predicate (UPPER compare over
-variant_sku/sku/dw_sku, base + `base-%`, exclude ARCHIVED, exclude the product being fixed). Result:
+Ran every one of the 61 confident products' corrected SKUs through the exact `skuTakenOnActiveProduct`
+predicate (UPPER compare over variant_sku/sku/dw_sku, base + `base-%`, exclude ARCHIVED) **with self
+excluded by normalized gid**. Result over the 68 confident variants:
 
-| corrected SKU | guard | blocker (why) |
+| verdict | count | meaning |
 |---|---|---|
-| `DIG_10082002-25-type-2-vinyl-Sample` | **CLEAR** | — |
-| `DIG-2400065-25-type-2-vinyl-Sample` | **CLEAR** | — |
-| `DIG-2400085-25-type-2-vinyl-Sample` | **CLEAR** | — |
-| `Dig-240426-Sample-25-type-2-vinyl-Sample` | **CLEAR** | — |
-| `DIG-435086-25-type-2-vinyl-Sample` | **CLEAR** | — |
-| `DIG-439241 Sample-25-type-2-vinyl-Sample` | **CLEAR** | — |
-| `DIG-68000-25-type-2-vinyl-Sample` | **CLEAR** | — |
-| `DIG-740108-25-type-2-vinyl-Sample` | **CLEAR** | — |
-| `DIG-74118-25-type-2-vinyl-Sample` | **CLEAR** | — |
-| `Vin-1100-25-type-2-vinyl-Sample` | **CLEAR** | — |
-| `DIG-171052-25-type-2-vinyl-Sample` | **BLOCKED** | squatted by 7664490577971 (Charlotte's black, own base 171051) mislabeled stray |
-| `DIG-171053-25-type-2-vinyl-Sample` | **BLOCKED** | the two group members themselves (self-chain) |
-| `DIG-1952-25-type-2-vinyl-Sample` | **BLOCKED** | squatted by 7664512761907 (Classic Book Shelf, own base Book-3001) mislabeled stray |
-| `DIG-2400089-25-type-2-vinyl-Sample` | **BLOCKED** | the two group members themselves |
-| `DIG-40021-25-type-2-vinyl-Sample` | **BLOCKED** | the two group members themselves |
-| `Dig-7700743-25-type-2-vinyl-Sample` | **BLOCKED** | the two group members themselves |
-| `DIG-85335-25-type-2-vinyl-Sample` | **BLOCKED** | squatted by 7664512401459 mislabeled stray |
-| `DIG-98621-25-type-2-vinyl-Sample` | **BLOCKED** | squatted by 7664512630835 mislabeled stray |
-
-**Reading:** 10/18 corrected SKUs are guard-clear today. The 8 "BLOCKED" are blocked either by the group's own
-un-fixed partner or by a THIRD product's un-fixed stray — they **self-clear once the whole stray family is
-swept in one coordinated pass** (the blocker's stray gets corrected in the same sweep, freeing the target).
-This is exactly why the STRAY fix must be a **family sweep, not 20 independent writes** — and why they are
-NOT presented as one-shot guard-clear mints. Any BLOCKED SKU that is STILL blocked after the sweep enumerates
-the full family = a real leftover collision → keep that variant's product DRAFT + tag, don't force it.
+| **CLEAR today** | 44 | target free right now |
+| **BLOCKED today, blocker IS an in-sweep family member** | 26 | self-clears in the same coordinated sweep |
+| **BLOCKED today by an OUT-of-sweep foreigner** | **0** | no real leftover collision in the confident set |
+| post-sweep residual true-dup | **0** (the 7 were moved to §2C REVIEW) | confident set is clean |
+
+The 26 "BLOCKED today" are the daisy-chain: product A's target is currently squatted by B's un-fixed stray,
+and B is itself in the sweep — so correcting B in the same batch frees A. The reconciliation is the
+source of truth (`final-verdict.json`), replacing v1's Section-3 table which was wrong (self never excluded).
 
 ---
 
-## SECTION 4 — Settlement / private-label flags (note, do NOT act)
+## SECTION 4 — Settlement flags across ALL 105 in-scope (fix #6)
 
-- **Private-label leak:** none of the 23 surface a private-label upstream name customer-facing. All are
-  `DW Bespoke Studio` (a first-party house line) → no Command54/WallQuest/Momentum-style leak risk. The
-  memo-twin (2C) reads `| DW Bespoke Studios` in its title (acceptable house brand), not a real vendor.
-- **Settlement-sensitive imagery (gate before any republish/activation, not part of re-SKU write):**
-  - `7774188404787` **"Nocturnal Anthurium Tropical"** (group 2B `Dig-240417`) — tropical/anthurium foliage → run `settlement` gate before it is touched for any status/image change.
-  - `7664488218675` **"Cranes in the Cherry Blossom Lake" (Japonisme)** (STRAY group 8) — bird (crane) motif → settlement gate before any republish.
-  - `7664598614067` **"Gregorious Gulls"** / `7664616800307` **"Zepellin Zebras"** (2B `DIG-762124`) — bird (gull) + animal motifs → settlement gate before any republish.
-  Re-SKU of a variant string does NOT change imagery, so the gate is only relevant IF a downstream step
-  re-activates or re-images these; flag carried so it isn't forgotten.
+Re-scanned every in-scope title for tropical / bird / butterfly / animal / Chinoiserie / botanical imagery.
+**26 settlement-sensitive products** (full list, must clear the `settlement` gate before ANY
+republish/reactivation/reimage):
+
+Barone's Butterfly ×4 (`7664511549491`, `7664511418419`, `7664511451187`, `7664511582259`) ·
+Carmellos Floral Mural (`7664512270387`) · Cerezo Trees Blossom (`7664488284211`) ·
+Charlie's Bamboo Chinoiserie (`7664585867315`) · Classic green floral vintage (`7664512827443`) ·
+**Cranes in the Cherry Blossom (`7664488218675`)** · Duquesne Chinoiserie (`7664512303155`) ·
+Eddington Floral (`7664487890995`) · Fiona's Floral (`7664591568947`) · Flapper Floral Stripes (`7664583704627`) ·
+Fremont Fern (`7664592846899`) · **Gregorious Gulls (`7664598614067`)** · Minimalist Simple Leaves ×2
+(`7664493002803`, `7315944046643`) · **Nocturnal Anthurium Tropical (`7774188404787`)** ·
+Patriotic Gun Acanthus Leaf (`7535350022195`) · Rosie's Roses ×2 (`7664629514291`, `7664593633331`) ·
+Waitsfield Floral Lattice (`7582548623411`) · Wanda's Wisteria ×2 (`7664593371187`, `7664593338419`) ·
+**Willington 1940's Branches Chinoiserie (`7664636100659`)** · **Zepellin Zebras (`7664616800307`)**.
+
+**Policy (Steve's call — presented as a distinct lever):** a re-SKU of a variant STRING changes no imagery,
+so the SKU fix is imagery-neutral. Per red-team fix #6, the conservative posture is to **DRAFT-HOLD these 26
+until the settlement gate clears, independently of the SKU fix.** Recommended default = **hold the 26 out of
+the sweep** (fix only their partners' non-settlement variants), so no settlement product is touched pre-gate.
+
+**Private-label leak:** 0 — all are `DW Bespoke Studio` (first-party house line); the Periwinkle twin's
+`| DW Bespoke Studios` title is an acceptable house brand, not an upstream vendor.
 
 ---
 
-## SECTION 5 — Exact write ops the apply pass WOULD fire (mechanical; PG-BEFORE-Shopify)
+## SECTION 5 — Idempotent, resumable, acyclic sweep procedure (fix #5)
+
+Nothing below has run. Order: **PostgreSQL mirror first, Shopify authoritative second**; batches ≥ 90s apart;
+status stays ACTIVE (only a variant SKU string changes).
+
+**Proof of acyclicity.** Over the 61 confident products, the write set = {each product ← its own unique
+base's `-Sample`}. Verified: **0 corrected-SKU targets are shared by >1 confident product**, so no two writes
+contend the same target ⇒ the write graph is a set of disjoint single-writer edges ⇒ **no cycle exists**.
+Ordering is therefore only an optimization, not a correctness requirement.
 
-Nothing below has run. Per standing rule: **PostgreSQL mirror first, Shopify authoritative second**; batches
-≥ 90s apart; never ACTIVE→other status here (these stay ACTIVE — only a variant SKU string changes).
+**Ordering (optimization):** topological — write every product whose target is CLEAR-today first (the 44),
+then re-run; each pass frees more of the 26 chained targets. Converges in ≤ 2 passes because the chain is
+linear.
 
-**Per STRAY member (×20, as ONE coordinated family sweep — see §2A/§3):**
-1. **Enumerate LIVE first (apply-time step 0):** fetch every ACTIVE `DW Bespoke Studio` product carrying a
-   `…-type-2-vinyl-Sample` (or peel/vinyl) variant whose SKU base ≠ the product's own 2x12/2x27 base. This is
-   the full stray family (superset of the 20); the 20 are the subset that currently collide pairwise.
-2. **Mirror (write, local):** `UPDATE shopify_products SET variant_sku=<corrected>, sku=<corrected>, synced_at=now() WHERE shopify_id=<gid>;`
-   (mirror stores the sampled variant SKU on both `variant_sku` and `sku`; update both so the guard reads clean).
-3. **Shopify (write, authoritative):** `productVariantsBulkUpdate(productId:<gid>, variants:[{ id:<stray variant gid>, inventoryItem:{ sku:"<corrected>" } }])` — SKU mutation goes through `inventoryItem.sku` per the store's standing rule. Only the ONE stray variant per product is touched; the other 15 are untouched.
-4. Re-verify guard-clear post-write; log to ticket + audit JSON.
+**Per confident product (×61, one batch, resumable):**
+1. **Mirror (local write):** `UPDATE shopify_products SET variant_sku=<corrected>, sku=<corrected>, synced_at=now() WHERE shopify_id='gid://shopify/Product/<pid>';`
+2. **Shopify (authoritative write):** `productVariantsBulkUpdate(productId:<gid>, variants:[{ id:<stray variant gid>, inventoryItem:{ sku:"<corrected>" } }])`. Only the 1–2 stray variants per product are touched.
+3. Re-run the FIXED guard on the corrected SKU; append `{pid, before, after, guard, ts}` to `apply-audit.jsonl`.
 
-Order the sweep so every "blocker" stray is corrected in the same batch as its "blocked" target (topological
-order by dependency) — or simplest: correct ALL strays to their own bases in a single bulk pass, then verify
-zero residual collisions. Because each corrected SKU is unique to its own product's base and each product
-already owns the rest of that base's variant set, a single all-at-once pass converges.
+**Idempotency:** step 1/2 are keyed on the stray VARIANT gid and set an exact target string — re-running a
+completed product is a no-op (SKU already equals target; guard already CLEAR). A resume reads `apply-audit.jsonl`,
+skips any pid whose last audit row shows `after==target && guard==CLEAR`, and re-attempts the rest.
 
-**REVIEW-FULLDUP (12 groups) — NO write proposed here.** Each needs a per-group base decision (which member
-keeps the base; the other gets a color/name-suffixed NEW base, verified guard-clear before mint). Two are
-possible TRUE dupes (`DIG-553801`, `DIG-5531287`) that a SKU-analyst may resolve by ARCHIVE-one instead of
-re-SKU. `-25-gold-2x27` (empty base) needs two brand-new DW SKUs minted from a real mfr# (unknown → REVIEW).
+**Partial-failure handling:** if a Shopify write fails mid-sweep, the mirror update for that pid is rolled
+back (or the pid is re-queued) so mirror never leads Shopify into a false-clean. **Any variant still BLOCKED
+after the full sweep enumerates = a real leftover collision → set that product DRAFT + tag `Needs-SKU-Review`,
+never force the write.** (Per §3 this should be 0, but the rail is enforced regardless.)
 
-**ROUTE (1 group) — NO write here.** Hand `DIG-4350959-Periwinkle - Sample` twin (7801847447603) to the
-null-Sample / memo-sample merge project.
+**REVIEW-stray (§2B+§2C, 10) — NO write.** Malformed-base + residual-true-dup need a per-product base
+decision (rebase vs archive-one). **ROUTE (§2D, Periwinkle twin) — NO write; hand to the memo-merge project.**
+**FULLDUP/empty (§2D, ~32) — NO write; SKU-analyst mints a suffixed base or archives a true dup.**
 
 ---
 
 ## SECTION 6 — Counts (for the ruling)
 
-| bucket | groups | products | disposition |
+| bucket | products | variants | disposition |
 |---|---|---|---|
-| STRAY (confident deterministic fix) | 10 | 20 | re-SKU stray variant to own base — **sweep as family** |
-| REVIEW-FULLDUP (needs judgment) | 12 | 25 | duplicate base upstream — SKU-analyst/Steve call |
-| ROUTE (memo-sample twin) | 1 | 2 (1 real + 1 twin) | route twin to null-Sample merge project |
-| **TOTAL live collision groups** | **23** | **47** | (vs stale 32; 9 self-resolved) |
-
-- **Confident unique-SKU proposal:** 10 STRAY groups / 20 products (all corrected SKUs derived + guard-checked;
-  10 clear today, 8 self-clear on family sweep, plus 2 self-chain).
-- **REVIEW (assign-or-DRAFT, never guessed):** 12 groups / 25 products.
-- **Route to PR/memo merge project:** 1 group (the `DIG-4350959-Periwinkle` twin).
-- **Settlement flags:** 4 products (Anthurium, Cranes, Gulls, Zebras) — note only, gate before any republish.
-- **Private-label leaks:** 0.
+| **CONFIDENT-STRAY** | **61** | **68** | mechanical re-SKU, **provably-acyclic family sweep** |
+| REVIEW-stray (malformed base) | 3 | 5 | SKU-analyst (no clean graft) |
+| REVIEW-stray (residual true-dup) | 7 | ~9 | rebase / archive-one |
+| REVIEW-FULLDUP / empty-base | ~32 | — | mint suffixed base or archive true dup |
+| ROUTE (Periwinkle memo twin) | 1 (+1 real) | — | route twin to null-Sample merge project |
+| **TOTAL in scope** | **105** | — | — |
+| Settlement-held (subset, cross-bucket) | 26 | — | DRAFT-hold until settlement gate clears |
+| Private-label leaks | 0 | — | — |
+
+---
+
+## SECTION 7 — Approval structure for Steve
+
+- **(A) Approve ALL** — sweep the 61 confident-STRAY (acyclic, idempotent) **AND** hand the 42 REVIEW/FULLDUP/route to their owners.
+- **(B) Approve all EXCEPT REVIEW** — run the 61-product confident sweep now; hold the 10 REVIEW-stray + 32 FULLDUP/empty + route for judgment.
+- **(C) Approve all EXCEPT settlement-held** — run the confident sweep but exclude the 26 settlement products (recommended default; touch their non-settlement partners only).
+- **(D) Line-item** — per-product from `final-verdict.json` / `strays-v3.json`.
 
 ---
 
 ## Data artifacts (git-tracked, this dir)
-- `probe-variants.mjs` — read-only live Shopify variant probe (used to derive every own-base + stray).
-- `build-proposals.mjs` — proposal builder + guard-mirror (needs `pg`; guard check also reproduced in-SQL).
-- `proposals.json` — machine-readable proposal rows (if regenerated with pg on PATH).
-- Guard verification: reproducible via the Section-3 SQL against the live `dw_unified` mirror.
+- `scan-family-v2.mjs` — the 935-product live variant scan (bounds the family). Output `family-v2.json`.
+- `classify-strays-v3.mjs` — correct core-identity stray classifier. Output `strays-v3.json` (71 products / 81 strays).
+- `final-verdict.mjs` — confident/REVIEW split + FIXED guard (gid-normalized) + acyclicity. Output `final-verdict.json`.
+- `in-scope-pids.json` — the 105-product scope set.
+- `bespoke_active_all.tsv` — the 935 ACTIVE Bespoke pids scanned.
+- `build-proposals.mjs`, `enumerate-family.mjs`, `family.json`, `candidates_raw.tsv` — v1 artifacts (superseded; kept for provenance).
 
 ## Cost
-`$0 (local)` — PG mirror reads, git, read-only Shopify GraphQL (~50 product reads). No paid-model/metered calls.
+`$0 (local)` — PG mirror reads, git, read-only Shopify GraphQL (~935 product reads). No paid-model/metered calls.

← 015da3d7 auto-save: 2026-07-30T09:46:44 (13 files) — tk10002-resku/be  ·  back to Ticket System  ·  auto-save: 2026-07-30T10:16:54 (3 files) — tk10002-resku/app 1eb5274f →