← back to Designer Wallcoverings

onboarding/sangetsu-lilycolor/PUBLISH-PREP-063026A.md

176 lines

# PUBLISH-PREP — code `063026A` (Sangetsu + Lilycolor)

**Status: PREP-ONLY / DECISION-READY DRAFT. Nothing written to dw_unified/Shopify.**
No SKUs registered in `dw_unified`, no products activated. Everything below is a PROPOSAL
awaiting Steve's explicit go on each gated item — EXCEPT the prefix decision, now settled:

> ✅ **2026-07-01 — Steve APPROVED `DWGO` as the Sangetsu/Goodrich prefix** (`DWSG` rejected: `S`
> is a forbidden letter). `DWLC` for Lilycolor stands. The prefix is stamped onto the offline
> Sangetsu staging (`vendor_prefix: "DWGO"`); the collision-safe numeric `DWGO-<seq>` assignment
> is still deferred to the gated `registerSku` step at publish time (a `dw_unified` write).

Companion handoff: `RESUME-063026A.md`. Dedup evidence: `staging/dedup-report.json`,
`staging/dedup-matches.tsv`.

---

## 0. TL;DR (the two things that block go)

1. ~~**`DWSG` is NON-COMPLIANT**~~ — RESOLVED 2026-07-01: Steve approved **`DWGO`** for
   Sangetsu/Goodrich (both letters allowed, unused, ties to Goodrich). `DWLC` for Lilycolor stands.
   No longer a blocker.
2. **No confirmed DW net cost exists.** The staged `¥` is Japan **LIST** price, not our cost.
   Per the Activation Gate this alone keeps every one of the 12,806 SKUs **HELD / draft**.
   Only Steve/purchasing can clear it.

---

## 1. Recommended DW-SKU prefixes (with rule-compliance proof)

### The rule (verified against live code, not just CLAUDE.md)
- **Source of truth:** `DW-Agents/vendor-command-center/server.js` → `generateCompliantPrefix()`
  (lines 772–803) and `getNextSkuRangeStart()` (lines 805–813). Cross-referenced with
  `shopify/scripts/lib/sku-registry.js` (header comment lines 7–14, `VENDOR_RANGES` lines 39–75).
- **Format:** `DW` + 2 letters + `-` + sequential number. Letters only, no digits in the prefix.
- **ALLOWED letter set (exact string in the code):** `ACEGHIJKLOPQRTUWXYZ`
  → `A C E G H I J K L O P Q R T U W X Y Z` (19 letters).
- **FORBIDDEN (hard to hear on phone):** `B D S F M N V`.

### Lilycolor → `DWLC` ✅ COMPLIANT (recommended)
| Letter | In ALLOWED set? |
|---|---|
| `L` | ✅ yes |
| `C` | ✅ yes |

- Not present in legacy `VENDOR_RANGES` and not a documented private-label prefix → free to reserve.
- Mnemonic maps cleanly to **L**ily**C**olor. **Recommend: keep `DWLC`.**

### Sangetsu → `DWSG` ❌ NON-COMPLIANT — FLAGGED
| Letter | In ALLOWED set? |
|---|---|
| `S` | ❌ **NO — `S` is a FORBIDDEN letter** |
| `G` | ✅ yes |

`DWSG` violates the forbidden-letter rule on its first letter. It must be replaced.

#### Compliant alternatives for Sangetsu (all letters from ALLOWED, none collide with legacy prefixes)
| Candidate | Letters (both allowed?) | Mnemonic | Legacy collision? | Note |
|---|---|---|---|---|
| **`DWGO`** ⭐ | `G`✅ `O`✅ | **G**oodrich (the importer/vendor entity we file Sangetsu under) | none | **RECOMMENDED** |
| `DWGT` | `G`✅ `T`✅ | san**G**e**T**su | none | good phonetic fit |
| `DWGU` | `G`✅ `U`✅ | **G**oodrich (alt) | none | fine fallback |

**Recommendation: `DWGO`.** It ties to Goodrich (the real vendor-registry entity — RESUME rule
"Goodrich = Sangetsu, never a separate vendor"), keeps the vendor obfuscated on the phone, and
both letters are compliant. `DWGT` is a fine second choice if Steve prefers a Sangetsu-phonetic mnemonic.

> ⚠️ Implementation note: the live `generateCompliantPrefix()` generates a **random** 2-letter combo
> and only guarantees it avoids collisions — it does **not** accept a hand-picked mnemonic. To use a
> chosen prefix (`DWLC` / `DWGO`), it must be **pre-set** in `vendor_registry.sku_prefix` for that
> vendor row BEFORE assign-skus runs; the assign flow (server.js line 846) honors an already-set
> prefix and only auto-generates when the field is blank. That row-set is a `dw_unified` write and is
> therefore **Steve-gated** (see §5).

---

## 2. Sangetsu short-numeric collision-storage rule (restated concretely)

**HARD RULE: every Sangetsu/Goodrich SKU is stored WITH its DW prefix — the raw mfr SKU is NEVER
used bare as a lookup key.** Sangetsu ships short, purely-numeric mfr SKUs (e.g. `3367`, `359000`,
`3373`, `316014`) and short alnum SKUs (`AZ52680`, `G0167`, `LX1000`) that collide as **strings**
with unrelated SKUs already in `dw_unified` under other vendors.

**Evidence (all false positives, from `dedup-matches.tsv`):**
- `359000` (Sangetsu) collides with **Brewster** `DWBR-170136` AND **LA Walls** `CGS-83552`.
- `3367` (Sangetsu) collides with **Phillip Jeffries** `DWJP-10875`.
- `316014` (Sangetsu) collides with **Brewster** `DWBR-170059`.
- 85 Brewster + 8 Phillip Jeffries + 6 LA Walls + others = **164 cross-vendor string collisions,
  0 true duplicates** (`dedup-report.json`: `true_same_vendor_duplicates: 0`).

**Concrete storage contract:**
1. In `dw_sku_registry`, the **`mfr_sku`** column holds the raw Sangetsu SKU (`3367`), the
   **`vendor_prefix`** column holds `DWGO`, and the assigned **`dw_sku`** is `DWGO-<seq>` (e.g.
   `DWGO-195000`). The duplicate check keys on `(vendor_prefix, mfr_sku)` together
   (`sku-registry.js` `checkDuplicate`, line 89–93) — so `('DWGO','3367')` never collides with
   `('DWPJ','3367')`.
2. Anywhere a bare mfr SKU could be used as a key (dedup joins, Shopify SKU field, image filenames,
   metafield `custom.manufacturer_sku`), it is **always scoped by the `DWGO` prefix**. Never let a
   bare `3367` become a join key against the global SKU space.
3. This is exactly why the 164 raw matches are provably false positives and all 12,806 SKUs are
   net-new importable.

---

## 3. Sequential-numbering plan

### How the live code numbers (verified)
- **Range blocks are 5,000 wide.** `getNextSkuRangeStart()` (server.js 806–813) reads
  `MAX(sku_range_start)` across `vendor_registry` (floor `195000`) and rounds up to the next 5,000
  boundary: `Math.ceil((maxStart + 1) / 5000) * 5000`.
- **Within a vendor, numbers are sequential.** `sku-registry.js` `getNextSkuNumber` (115–136) takes
  `MAX(numeric suffix)+1`; `registerProductLine` (192–242) spaces colorways by **10** so future
  insertions fit between existing colorways.

### Proposed allocation (PROPOSAL — exact starts computed at reservation time)
| Vendor | Prefix | SKU count | Block sizing |
|---|---|---|---|
| Lilycolor | `DWLC` | 2,543 | needs ≥ 1 block (5,000). One 5,000 block suffices. |
| Sangetsu/Goodrich | `DWGO` | 10,263 | needs ≥ 3 blocks (15,000) to hold 10,263 with head-room. |

- **Do not hard-code the start numbers now.** The real starts come from `getNextSkuRangeStart()` at
  reservation time so the two new vendors slot in *after* the current max (the RESUME source note
  puts the registry at ~79,924 registry rows / 156,481 shopify SKUs, and `vendor_registry`
  `sku_range_start` max drives the block). Reserve **`DWLC` first, then `DWGO`**, so `DWGO` starts a
  full 5,000 block after `DWLC`'s block(s), preserving the "each vendor starts ahead of the last" rule.
- **Colorway spacing:** use `registerProductLine` (spacing 10) for Sangetsu, since its data is
  pattern→colorway (`sangetsu-staging.jsonl`: 622 patterns → 10,263 `colorway_skus`). This keeps
  each pattern's colorways contiguous and leaves gaps for later colorway additions.

---

## 4. THE GATE — cost sourcing must clear before ANY activation

**Blocker:** the staged price is **Japan LIST price in ¥** (`list_price_yen`, `m2_price_yen` in
`lilycolor-unified-staging.jsonl`), which is **NOT** our confirmed DW net cost. Every staged record
already carries `cost_confirmed: false` and `activation_ready: false`.

- Per the Activation Gate (`shopify/scripts/lib/validate-before-activate.js` philosophy + the
  Kravet-style cost→retail rule), **no confirmed cost → the SKU cannot be priced → it stays
  HELD/draft.** We will not derive a retail from a foreign LIST price.
- **Only Steve/purchasing supplies real DW net cost** (negotiated wholesale, FX, freight, duty).
  Until that lands per SKU (or per collection), all 12,806 SKUs remain **draft**, even after prefix
  reservation and even if specs/images/descriptions are complete.
- Nothing about prefix assignment or dedup changes this. Prefix reservation is a *cataloging* step;
  **cost is the *activation* gate**, and it is independent and hard.

---

## 5. Explicit checklist — what needs Steve's GO before anything is written

Nothing below has been done. Each is gated.

- [ ] **Approve Lilycolor prefix `DWLC`** (compliant; recommended).
- [ ] **Approve Sangetsu prefix `DWGO`** (replacement for the non-compliant `DWSG`). Alt: `DWGT` / `DWGU`.
- [ ] **GO to write the two `vendor_registry` rows** (`sku_prefix` + `sku_range_start`) — this is a
      `dw_unified` write, needs explicit approval. Reserve `DWLC` first, then `DWGO`.
- [ ] **GO to run SKU registration** (`registerSku` / `registerProductLine`) for the 12,806 SKUs into
      `dw_sku_registry` — write, gated. Sangetsu via `registerProductLine` (spacing 10), scoped by
      `DWGO`, raw numeric SKU in `mfr_sku`.
- [ ] **Supply confirmed DW net cost** (Steve/purchasing) — the HARD activation blocker. Until then,
      everything imports as **draft** only.
- [ ] **GO for JA→EN title translation** staging (read-only alongside Japanese) — proposed next leg.
- [ ] **GO for LOCAL design enrichment** (interior-designer style/color/hex via local qwen2.5vl,
      no paid Gemini) — read-only staging.
- [ ] **GO for Shopify publish** via metered `cadence-import.js` only — never burst, Steve-gated,
      and only after cost clears the Activation Gate.

---

### Provenance
- VCC prefix logic: `DW-Agents/vendor-command-center/server.js` lines **772–813** (`generateCompliantPrefix`, `getNextSkuRangeStart`) and assign flow **816–858**.
- Registry / numbering: `shopify/scripts/lib/sku-registry.js` (`VENDOR_RANGES`, `getNextSkuNumber`, `registerProductLine`).
- Dedup: `staging/dedup-report.json` (164 cross-vendor false positives, 0 true dups), `staging/dedup-matches.tsv`.
- Handoff: `RESUME-063026A.md`.

_Generated 2026-07-01 · PREP-ONLY · no DB/Shopify writes._