← back to Dw Sku Integrity

apply-plans/phillip-jeffries-tk11131/README.md

113 lines

# TK-11131 — 897/900 Phillip Jeffries mfr_sku backfill

> **⚠️ STATUS UNCERTAIN (as of 2026-09-15) — this apply APPEARS TO HAVE ALREADY
> EXECUTED against LIVE Shopify.** The on-disk evidence points that way but is not
> conclusive:
> - `undo-map.json` is fully populated (all 897 entries) and `undo-map-3-ambiguous.json`
>   holds its 3 entries. In the current scripts the undo-map is only written on the
>   `--apply` path (the `--dry-run` path exits before writing it).
> - The file mtimes show each `undo-map*.json` written shortly AFTER its
>   corresponding `apply*.mjs` script — the signature of a real `--apply` run.
> - `apply-3-ambiguous.mjs`'s own header comment states it was "executed with
>   Steve's explicit go."
>
> This is **not proof** — the undo-map was historically written speculatively (see
> the reliability fix in the scripts), so a populated map could in principle predate
> a confirmed write. **CONFIRM WITH STEVE before re-running or re-approving** — a
> second `--apply` is largely idempotent (both metafields already set → skipped), but
> the canonical `dw_unified` write promised below (see "Known gap") was never
> performed by this code and must be reconciled either way.

Customer-facing Shopify metafield write + canonical Kamatera
`shopify_products`/`pj_catalog` write. Any (re-)execution needs Steve's explicit go
per the standing hard rule (never auto-fire a customer-facing/canonical write).

## Known gap — dw_unified was never written by this code
The scripts here (`apply.graphql.mjs`, `apply-3-ambiguous.mjs`) write **only** the
two Shopify metafields. The "Target fields" section below and the standing PG-first
doctrine promise a matching canonical `dw_unified` write
(`pj_catalog.mfr_sku` / `shopify_products`) that **this code never performs**. If the
Shopify write has already run (see status above), `dw_unified` is now diverged
(split-brain). A gated reconcile memo has been drafted to
`~/.claude/yolo-queue/pending-approval/` — do NOT reconcile without Steve.

## What this recovers
The 900 Phillip Jeffries products that went ACTIVE on 2026-09-02 with no real
manufacturer code (see `~/.claude/yolo-queue/pending-approval/2026-09-03-TK-11131-pj-golive-gate-violation-500skus.md`
for the original violation writeup). Per `dw-sku-integrity` doctrine — never
mint a new code, always recover-or-rescrape — this is the **rescrape**: real
codes pulled live from phillipjeffries.com's own product-search index, not
invented.

## How the codes were found
1. `phillipjeffries.com/shop/wallcoverings` loads an Algolia-backed product
   grid (`algoliasearch@4.21.1` + `instantsearch.js@4.62.0`).
2. App ID/key are inline in the page (`algolia_app_id='HLJ09W6VF2'`,
   `algolia_key='5545b74f6bb6e93487ff5482ee7f6182'` — this is a
   client-side **search-only** key, publicly shipped in PJ's own page source,
   not a secret we obtained improperly).
3. Index name resolves from `algolia_env` ('production' on the live page) to
   `pj_grid` (vs `DEV_pj_grid` in dev) — read straight out of PJ's own
   minified JS bundle (`algolia-product-grid-*.js`).
4. Queried `pj_grid` directly (`POST https://HLJ09W6VF2-dsn.algolia.net/1/indexes/pj_grid/query`)
   — **6,107 live records**, far more than the static `sitemap.xml` (5,937
   URLs) or the stale `artmura/philipjeffries.json` capture (2,447 products,
   zero matches against this batch — confirms these are patterns PJ added
   since that capture).
5. Matched all 900 flagged Shopify titles ("Pattern - Colorway") against
   Algolia's `product_line` + `colorway` fields (normalized, case/punct
   insensitive). **897/900 matched to exactly one record. 3 matched to
   multiple candidates** (see Ambiguous below) — no misses.

## Format triangulated 3 independent ways
- Algolia `ITEM` field (numeric, e.g. `34504`, `2943`).
- `dw_unified.pj_catalog.mfr_sku` (existing compliant rows) — same numeric
  format (e.g. `10287`), confirmed to be the exact same PJ item as
  Algolia ITEM 10287 ("Suede Lounge - Taupe Topaz").
- **Live Shopify metafield on a July-2026 compliant PJ product**
  ("Marfa Weave, Navy Oasis" — `gid://shopify/Product/7880407416883`):
  `custom.manufacturer_sku` = `global.manufacturer_sku` = `"2943"`, which is
  exactly Algolia ITEM 2943 ("Marfa Weave" / "Navy Oasis") — an independent
  match I did not derive the mapping from. This is the CURRENT pipeline's
  convention (a July-2025-era product with a *different*, older 2024
  reference — `DWJP-14510`/GV238 in `global."mfr pattern"` — uses a legacy
  alphanumeric PJ pattern-catalog code instead; both are genuinely PJ codes
  from different eras of PJ's own numbering, but the numeric ITEM/
  `manufacturer_sku` scheme is what the pipeline currently writes).

## Target fields to backfill (per the July-2026 compliant reference product)
- `custom.manufacturer_sku` = `<real numeric code>`
- `global.manufacturer_sku` = `<real numeric code>` (mirrored, both namespaces)
- Canonical `dw_unified` mirror: `shopify_products` (Kamatera-canonical) /
  `pj_catalog.mfr_sku` equivalent — write order per standing doctrine is
  PG-stage-first then Shopify, Shopify write is the authoritative one.

## Files here
- `backfill-mapping-897.json` / `.csv` — sku, handle, shopify product GID,
  title, `real_mfr_sku`, `product_line`, `colorway` for the 897 clean matches.
- `ambiguous-3.json` — 3 "Savile Suiting Pinstripe" SKUs (DWJP-15047/48/49)
  where the title matched multiple live PJ ITEM codes (the II-collection vs
  original-collection variants share the same displayed name). Needs a
  manual pick (image/width compare) or a direct ask to PJ rep — NOT
  auto-resolved. Flagged as PROVENANCE_REVIEW per doctrine.
- `apply.graphql.mjs` — the script that sets both metafields on the 897 products
  via Shopify Admin GraphQL (`metafieldsSet`), idempotent PER FIELD: `custom` and
  `global` are evaluated and written independently, so a row with only one field
  populated still gets the other backfilled and an already-set field is never
  overwritten (the "only writes where the field is currently empty" contract; same
  spirit as the `IS NULL`-style guard in the sibling `phillip-jeffries/` dir).
  **May have already run — see STATUS UNCERTAIN at the top.**

## To (re-)execute — CONFIRM STATUS WITH STEVE FIRST
This apply appears to have already executed (see STATUS UNCERTAIN above). Do NOT
re-run or re-approve until Steve confirms whether the live write already happened.
If confirmed not-yet-run (or a deliberate idempotent re-apply):
```
node apply.graphql.mjs --dry-run     # prints what WOULD be written, no writes
node apply.graphql.mjs --apply       # executes, logs to executed-reversible ledger
```
Reversible: undo = clear the written metafields back to null. The undo-map is now
built from the SET-batch SUCCESS results (only rows actually written) and flushed
after each successful batch, so `undo-map.json` is a reliable rollback source —
records what was actually applied, not merely what was attempted.