← back to Dw Unbuyable Recovery Pilot
tk11042-quote-tag/README.md
54 lines
# TK-11042 — quote-CTA tag normalization
## What this fixes
`snippets/dw-quote-only-cta.liquid` renders the "Request a Quote" CTA **only** when
`product.tags contains 'quotes'` (exact Liquid array membership; the snippet header
states `quotes` was deliberately chosen over `quote_only` / `Contact for Price`).
Products carrying quote *intent* under a different tag name — `quote-only`,
`contact-for-price`, `Needs-Price` — therefore render with **no price, no
Add-to-cart and no quote CTA**: a customer-facing dead end.
Live audit 2026-09-10 (Shopify Admin API, read-only):
| cohort | active | dead ends |
|---|---|---|
| Koroseal | 2,449 | 0 — 100% already carry `quotes`, correct-by-design |
| Maya Romanoff | 620 | 0 — 100% already carry `quotes` |
| Marburg | 112 | **112** — carry only `Needs-Price` |
| Phillipe Romano | 14,398 | **4,096** (of 4,167 flagged; 71 are correct-by-design memo-samples) |
**No vendor cost data is required for any of this.** Cost lists are only needed for
the separate, optional goal of converting contract quote-only lines to self-serve pricing.
## Tranches
- **Tranche 1 — 2,001** (Phillipe Romano): carry explicit `quote-only`/`contact-for-price`.
Deliberate quote-only posture; the tag vocabulary simply doesn't match the theme trigger.
- **Tranche 2 — 2,207** (2,095 PR + 112 Marburg): carry only `Needs-Price`.
Ambiguous — 1,277 of these also carry `Needs-Width` **and** `Needs-Image`, i.e. they look
like incomplete imports rather than a deliberate quote-only posture.
⚠️ `quotes` is also the **exemption tag** for `dw-five-field-canary`
(auditor.mjs L105-106), so tagging an incomplete import silences its data-quality alarm.
- **Excluded — 71**: single `Default Title` @ $4.25 memo-sample products. Live-verified
`available:true` — they sell as samples, correct-by-design, not dead ends.
## Usage
```sh
node build-plan.mjs # READ-ONLY, refresh data/plan.json
node repair.mjs --tranche=1 # dry-run (default)
node repair.mjs --tranche=1 --limit=20 --live # pilot
node repair.mjs --tranche=1 --live # full tranche (idempotent)
node rollback-all.mjs --tranche=1 --live # byte-for-byte tag restore
```
## Safety
- Adds exactly **one tag**. Touches no price, variant, inventory, or status.
- `repair.mjs` re-reads each product live and re-checks every precondition
(still active, still not sellable, intent tag still present, Sample variant
detectable, not already tagged) before writing — never trusts the plan.
- Records the exact prior `tags` string per product; rollback restores it byte-for-byte.
- Verified no `$0`-orderable exposure is created: the 1,281 `Per Yard` $0 variants are
`inventory_policy=deny` and render sold-out.
- Verified zero merchandising side-effects: 643 smart collections scanned, **none**
reference `quotes` / `Needs-Price` / `contact-for-price` in their rules.