[object Object]

← back to Dw Sku Integrity

TK-10896: add provenance guard — greenfield Phase-4 mint prefixes route to rescrape

2bcc1992d73b181b14455deb7685bef3a810f827 · 2026-08-30 09:33:26 -0700 · codex-10896

Verified via ticket event log + phase2-phase4 decision aid: the reverted Phase-4
mint allocated codes into greenfield prefixes (DWAG Carnegie, DWAX Maharam, DWCX,
DWST, DWSC, DWDX, DWWG) that had zero prior coded products. On the Mac2 mirror that
residue persists in sku; self-copying it would re-instate reverted mints. Guard
routes them to MINT_RESIDUE_RESCRAPE (8,897 rows). Corrected mirror segmentation:
25,623 self-copy / 8,962 rescrape / 9 dedup / 6 staging-link. 24 tests green.

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

Files touched

Diff

commit 2bcc1992d73b181b14455deb7685bef3a810f827
Author: codex-10896 <steve@designerwallcoverings.com>
Date:   Sun Aug 30 09:33:26 2026 -0700

    TK-10896: add provenance guard — greenfield Phase-4 mint prefixes route to rescrape
    
    Verified via ticket event log + phase2-phase4 decision aid: the reverted Phase-4
    mint allocated codes into greenfield prefixes (DWAG Carnegie, DWAX Maharam, DWCX,
    DWST, DWSC, DWDX, DWWG) that had zero prior coded products. On the Mac2 mirror that
    residue persists in sku; self-copying it would re-instate reverted mints. Guard
    routes them to MINT_RESIDUE_RESCRAPE (8,897 rows). Corrected mirror segmentation:
    25,623 self-copy / 8,962 rescrape / 9 dedup / 6 staging-link. 24 tests green.
    
    Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---
 README.md                            |  1 +
 classify.mjs                         | 36 ++++++++++++++-
 evidence/ANALYSIS-2026-08-30.md      | 87 +++++++++++++++++++-----------------
 evidence/mac2-mirror-2026-08-30.json | 19 +++++---
 test/classify.test.mjs               | 28 ++++++++++++
 5 files changed, 125 insertions(+), 46 deletions(-)

diff --git a/README.md b/README.md
index 953e2d4..b7a03c0 100644
--- a/README.md
+++ b/README.md
@@ -52,6 +52,7 @@ collision flag), suitable as the dry-run input to a *gated* apply step elsewhere
 | `SELF_COPY_CORK` | `sku` holds a `Cork-<n>` code | recoverable_now_self_copy |
 | `STAGING_LINK` | no usable `sku`, real `mfr_sku` present | recoverable_now_staging_link |
 | `SELF_COPY_*_COLLISION` | recovered code already live on another active product | dedup_gated_TK10649 |
+| `MINT_RESIDUE_RESCRAPE` | code sits in a reverted Phase-4 **greenfield** mint prefix (`DWAG/DWAX/DWCX/DWST/DWSC/DWDX/DWWG`) — self-copy would re-mint | rescrape_program_TK10900 |
 | `RESCRAPE` | no `sku`, no `mfr_sku` — must re-scrape the vendor | rescrape_program_TK10900 |
 | `IMPORT_DEFECT` | literal `null`/`null-Sample` sku (scraper bug) | rescrape_program_TK10900 |
 
diff --git a/classify.mjs b/classify.mjs
index 3c7a2a9..12e95d7 100644
--- a/classify.mjs
+++ b/classify.mjs
@@ -33,6 +33,33 @@ const SUFFIX_RE = new RegExp(
   'i'
 );
 
+// PROVENANCE GUARD — Phase-4 "greenfield" mint prefixes (TK-10896, verified via
+// the ticket event log + sku-integrity-phase2-phase4 decision aid, 2026-08-26).
+// These vendor prefixes had ZERO coded products before the reverted Phase-4 mint,
+// so ANY code in one of them now sitting in a blank row's `sku` is MINT RESIDUE,
+// not a scraper-native code. Self-copying it would silently re-instate a number
+// Steve explicitly reverted ("never mint"). Route these to re-scrape instead.
+// Bucket A prefixes (registered==already-in-use: DWKN/DWTT/DWRW/DWJS/DWRO/DWCC)
+// are intentionally NOT here — a code there may be scraper-native; disambiguating
+// them needs the exact reverted-mint number list (follow-up), so they stay
+// self-copy by default rather than over-blocking.
+export const GREENFIELD_MINT_PREFIXES = new Set([
+  'DWAG', // Carnegie
+  'DWAX', // Maharam
+  'DWCX', // CMO Paris
+  'DWST', // Stout Textiles
+  'DWSC', // Scalamandre
+  'DWDX', // Designtex
+  'DWWG', // Wolf Gordon
+]);
+
+function greenfieldPrefixOf(code, prefixSet) {
+  const m = /^(DW[A-Z0-9]{1,6})-/i.exec(code || '');
+  if (!m) return null;
+  const pfx = m[1].toUpperCase();
+  return prefixSet.has(pfx) ? pfx : null;
+}
+
 // A canonical DW code prefix, e.g. DWAK-, DWCC-, DWEL-RM-...
 const DW_CODE_RE = /^DW[A-Z0-9]{1,6}-/i;
 // A Cork- source code (Greenland / Phillipe Romano cork line — self-copy its Cork-<num>).
@@ -62,9 +89,11 @@ export function stripUnitSuffix(sku) {
  * @param {{dw_sku?:string, sku?:string, mfr_sku?:string, status?:string}} row
  * @param {Set<string>} [activeCodeSet] set of dw_sku values already live on an
  *        ACTIVE product — used to flag COLLISION (candidate already taken).
+ * @param {{greenfieldPrefixes?:Set<string>}} [opts] provenance-guard config.
  * @returns {{class:string, candidate:(string|null), collides:boolean, note?:string}}
  */
-export function classifyRow(row, activeCodeSet) {
+export function classifyRow(row, activeCodeSet, opts = {}) {
+  const greenfield = opts.greenfieldPrefixes || GREENFIELD_MINT_PREFIXES;
   const status = String(row.status || '').trim().toLowerCase();
   const dw = String(row.dw_sku || '').trim();
   if (dw) return { class: 'NOT_BLANK', candidate: null, collides: false };
@@ -84,6 +113,10 @@ export function classifyRow(row, activeCodeSet) {
     const cand = stripUnitSuffix(sku);
     if (!cand) return { class: 'RESCRAPE', candidate: null, collides: false, note: 'sku was all-suffix' };
     if (DW_CODE_RE.test(cand)) {
+      // Provenance guard FIRST: a greenfield-prefix code is reverted-mint residue,
+      // never scraper-native → must re-scrape, must NOT self-copy.
+      const gf = greenfieldPrefixOf(cand, greenfield);
+      if (gf) return { class: 'MINT_RESIDUE_RESCRAPE', candidate: null, collides: false, note: `greenfield mint prefix ${gf} (reverted Phase-4) — recover real mfr code, do not self-copy ${cand}` };
       const collides = has(cand);
       return { class: collides ? 'SELF_COPY_DW_COLLISION' : 'SELF_COPY_DW', candidate: cand, collides };
     }
@@ -111,6 +144,7 @@ export const RECOVERY_GROUP = {
   SELF_COPY_COLLISION: 'dedup_gated_TK10649',
   RESCRAPE: 'rescrape_program_TK10900',
   IMPORT_DEFECT: 'rescrape_program_TK10900',
+  MINT_RESIDUE_RESCRAPE: 'rescrape_program_TK10900',
   NOT_BLANK: 'out_of_scope',
   OUT_OF_SCOPE_STATUS: 'out_of_scope',
 };
diff --git a/evidence/ANALYSIS-2026-08-30.md b/evidence/ANALYSIS-2026-08-30.md
index 266dbf0..45bc921 100644
--- a/evidence/ANALYSIS-2026-08-30.md
+++ b/evidence/ANALYSIS-2026-08-30.md
@@ -10,59 +10,66 @@ Kamatera for the authoritative number.
 `dwsku-backlog-scan.mjs` runs the deterministic classifier (`classify.mjs`) over
 every ACTIVE product with a blank `dw_sku`. The classifier only ever proposes a
 candidate `dw_sku` that is **already present on the row** (recovered from `sku` by
-pure unit-suffix stripping) — it can never fabricate/mint one. 20 unit tests cover
-the strip rule, collision detection, and the no-mint invariant.
+pure unit-suffix stripping) — it can never fabricate/mint one. A **provenance guard**
+additionally blocks self-copy of reverted Phase-4 mint residue (see below). 24 unit
+tests cover the strip rule, collision detection, the no-mint invariant, and the guard.
 
 ## Headline segmentation (Mac2 mirror, 34,600 active-blank rows)
 
 | Recovery group | Rows | Route |
 |---|---:|---|
-| recoverable_now_self_copy | 34,520 (99.77%) | self-copy the existing code from `sku` |
+| recoverable_now_self_copy | 25,623 | self-copy the existing code from `sku` |
+| rescrape_program (TK-10900) | 8,962 | 8,897 mint-residue + 65 truly bare |
+| dedup_gated (TK-10649) | 9 | candidate collides w/ a live active code |
 | recoverable_now_staging_link | 6 | link `mfr_sku` → vendor `*_catalog.dw_sku` |
-| dedup_gated (TK-10649) | 9 | candidate collides w/ a live active code → verify+archive dup |
-| rescrape_program (TK-10900) | 65 | truly bare (no sku, no mfr): Pixels 52, Steve Abrams Studios 13 |
 
-By class: `SELF_COPY_DW` 31,469 · `SELF_COPY_SOURCE` 3,026 · `SELF_COPY_CORK` 25 ·
-`STAGING_LINK` 6 · `SELF_COPY_COLLISION` 6 · `SELF_COPY_DW_COLLISION` 3 · `RESCRAPE` 65.
+By class: `SELF_COPY_DW` 22,572 · `MINT_RESIDUE_RESCRAPE` 8,897 · `SELF_COPY_SOURCE`
+3,026 · `RESCRAPE` 65 · `SELF_COPY_CORK` 25 · `STAGING_LINK` 6 · `SELF_COPY_COLLISION`
+6 · `SELF_COPY_DW_COLLISION` 3.
 
-Self-copy provenance: **16,175** of the 34,520 self-copy rows also carry a real
-`mfr_sku` (the recovered code is cross-verifiable against the vendor staging
-catalog); 18,345 are sku-only (the code the row already holds is the only identifier).
+## The provenance guard — why 8,897 rows are NOT self-copy-eligible
 
-## Two findings that change the plan — for Steve
+A first pass (no guard) reported 34,520/34,600 self-copy-recoverable and only 65
+needing re-scrape — seemingly contradicting the scope memo's 12,589-rescrape /
+Carnegie-5,921 projection. **That was wrong, and here is the verified reason:**
 
-### 1. Mirror-vs-canonical drift (must verify on Kamatera)
-The scope memo (verified on Kamatera 2026-08-26) projected **12,589** Class-E rows
-needing the big per-vendor **re-scrape program**, with **Carnegie 5,921** as the
-anchor cohort. On the Mac2 mirror that program **collapses to 65 rows / 2 vendors** —
-every Carnegie/Maharam/Knoll/etc. blank row already holds its DW code in `sku`.
-Either (a) the Mac2 mirror is stale (never received Phase-1's writes / re-synced
-from Shopify), or (b) the re-scrape program is largely unnecessary. **Resolve by
-running `DWSKU_PSQL='ssh <kam> psql' node dwsku-backlog-scan.mjs` on Kamatera.**
+The ticket event log + the `sku-integrity-phase2-phase4` decision aid confirm the
+reverted **Phase-4 mint** allocated codes into **"greenfield" vendor prefixes that
+had ZERO coded products beforehand** — Carnegie `DWAG-`, Maharam `DWAX-`, CMO Paris
+`DWCX-`, Stout `DWST-`, Scalamandre `DWSC-`, Designtex `DWDX-`, Wolf Gordon `DWWG-`.
+Steve reverted the 11,725 mints ("rolled back to blank"), but on the **Mac2 mirror**
+the minted code persists in `sku`/`variant_sku`. So those `sku` values are **mint
+residue, not scraper-native codes** — self-copying them would silently re-instate the
+exact numbers Steve reverted (a "never mint" violation). The guard routes any
+greenfield-prefix code to `MINT_RESIDUE_RESCRAPE` (→ recover the real mfr code by
+re-scrape), never self-copy.
 
-### 2. Provenance of the `DWAG-`/mint-adjacent codes (self-copy safety)
-Carnegie's 5,921 blank rows carry `sku=DWAG-######` + a real `mfr_sku`
-(`100795352-panels`) + a `carnegie_catalog` staging row (mfr→dw_sku). `DWAG-` is
-the registry prefix the ticket flagged as **"unused"**, and Phase-4 *minted* into
-`DWAG-` before Steve reverted it. So the open question is whether these `DWAG-`
-codes are **scraper-assigned** (self-copy/staging-link is compliant) or **residue
-of the reverted Phase-4 mint** (self-copying re-instates a minted number — a
-doctrine violation). Read-only analysis on the mirror cannot settle provenance;
-this is a decision-gate for Steve. The 16,175 mfr-bearing self-copy rows are the
-ones where staging cross-verification can help answer it.
+Mint-residue by vendor: Carnegie 5,921 · Maharam 1,429 · Wolf Gordon 419 ·
+Scalamandre 418(+22) · CMO Paris 344 · Stout 180 · Designtex 164.
+
+This also **reconciles the mirror-vs-canonical drift**: on canonical Kamatera the
+revert cleared these codes → they read as bare/rescrape (matching the memo's 12,589);
+the Mac2 mirror still shows the residue in `sku`. The mirror is drifted in this
+specific way, and the guard makes the classifier robust to it either way.
 
 ## What is safe to conclude now
-- The classifier + strip rule are correct and reproducible (tests green; validated
-  against real sku shapes incl. `-Sample`, `-Yard`, `-Per Yard`, doubled `-Sample-Sample`,
+- Classifier + strip rule are correct and reproducible (24 tests green; validated on
+  real sku shapes incl. `-Sample`, `-Yard`, `-Per Yard`, doubled `-Sample-Sample`,
   and non-numeric Elitis `DWEL-RM-…` cores).
-- Nothing was written to any database. No code was minted.
+- Nothing was written to any database. No code was minted. Within-batch collision
+  check = 0 (no two distinct-title blanks collapse to one candidate).
 - The 9 collisions match the memo's Phase-2 dedup class (Novasuede `DWCC-*-Per Yard`,
-  Arte `DWKE-41415-Sample-Sample`) → route to TK-10649.
-- The only genuine re-scrape need on the mirror is 65 rows (Pixels + Steve Abrams).
+  Arte `DWKE-41415-Sample-Sample`) → TK-10649.
+- The re-scrape program is real (8,962 rows), dominated by Carnegie 5,921 — vindicating
+  the original scope memo, not the naive 65-row figure.
 
-## Next steps (all gated / out of this read-only scope)
-1. **Steve/drift-canary:** run the scanner on Kamatera → canonical segmentation.
-2. **Steve decision:** provenance ruling on the `DWAG-`/mint-adjacent cohort
-   (self-copy-OK vs treat-as-minted → re-scrape).
-3. On a self-copy-OK ruling, the write remains canonical/customer-facing → gated,
-   drafted to pending-approval per row batch, ledgered/reversible.
+## Residual open items (gated / out of this read-only scope)
+1. **Kamatera canonical run** — `DWSKU_PSQL='ssh <kam> psql' node dwsku-backlog-scan.mjs`
+   for the authoritative segmentation (the guard means the mirror and canonical should
+   now agree on routing even where `sku` residue differs).
+2. **Bucket A/C disambiguation** — greenfield (Bucket B) is unambiguous. For Bucket A
+   prefixes that were ALSO in scraper use (DWKN/DWTT/DWRW/DWJS/DWRO/DWCC, ~668 minted)
+   and the Romo `DWRO-30476..30966` reverted range, separating scraper-native from mint
+   requires the exact reverted-mint number list; until then those stay self-copy by
+   default (documented, low volume). Recommend sourcing the mint ledger to tighten this.
+3. Any `dw_sku` write is canonical + customer-facing → gated, per-vendor batch, ledgered.
diff --git a/evidence/mac2-mirror-2026-08-30.json b/evidence/mac2-mirror-2026-08-30.json
index 62aa49c..2675229 100644
--- a/evidence/mac2-mirror-2026-08-30.json
+++ b/evidence/mac2-mirror-2026-08-30.json
@@ -6,7 +6,8 @@
   "active_code_count": 55203,
   "active_blank_dwsku": 34600,
   "by_class": {
-    "SELF_COPY_DW": 31469,
+    "SELF_COPY_DW": 22572,
+    "MINT_RESIDUE_RESCRAPE": 8897,
     "SELF_COPY_SOURCE": 3026,
     "RESCRAPE": 65,
     "SELF_COPY_CORK": 25,
@@ -15,17 +16,25 @@
     "SELF_COPY_DW_COLLISION": 3
   },
   "by_recovery_group": {
-    "recoverable_now_self_copy": 34520,
-    "rescrape_program_TK10900": 65,
+    "recoverable_now_self_copy": 25623,
+    "rescrape_program_TK10900": 8962,
     "dedup_gated_TK10649": 9,
     "recoverable_now_staging_link": 6
   },
   "self_copy_provenance": {
-    "with_real_mfr_sku_cross_verifiable": 16175,
-    "sku_only_no_independent_check": 18345
+    "with_real_mfr_sku_cross_verifiable": 9814,
+    "sku_only_no_independent_check": 15809
   },
   "rescrape_cohorts_top": {
+    "Carnegie": 5921,
+    "Maharam": 1429,
+    "Wolf Gordon": 419,
+    "Scalamandre": 418,
+    "CMO Paris": 344,
+    "Stout Textiles": 180,
+    "Designtex": 164,
     "Pixels": 52,
+    "Scalamandre Wallpaper": 22,
     "Steve Abrams Studios": 13
   }
 }
diff --git a/test/classify.test.mjs b/test/classify.test.mjs
index e99e1e5..804ef0c 100644
--- a/test/classify.test.mjs
+++ b/test/classify.test.mjs
@@ -119,6 +119,34 @@ test('INVARIANT: classifier never proposes a candidate absent from the row (no m
   }
 });
 
+test('PROVENANCE GUARD: greenfield-prefix DWAG (Carnegie) -> MINT_RESIDUE_RESCRAPE, never self-copy', () => {
+  const r = classifyRow({ status: 'active', dw_sku: '', sku: 'DWAG-376566-Sample', mfr_sku: '100795352-panels' }, new Set());
+  assert.equal(r.class, 'MINT_RESIDUE_RESCRAPE');
+  assert.equal(r.candidate, null); // MUST NOT propose the minted code as a self-copy
+  assert.equal(RECOVERY_GROUP[r.class], 'rescrape_program_TK10900');
+});
+
+test('PROVENANCE GUARD: Maharam DWAX + Wolf Gordon DWWG greenfield -> rescrape', () => {
+  for (const sku of ['DWAX-100200-Yard', 'DWWG-55010-Roll']) {
+    const r = classifyRow({ status: 'active', dw_sku: '', sku }, new Set());
+    assert.equal(r.class, 'MINT_RESIDUE_RESCRAPE', `${sku} should be mint residue`);
+    assert.equal(r.candidate, null);
+  }
+});
+
+test('PROVENANCE GUARD: a NON-greenfield DW prefix stays SELF_COPY_DW', () => {
+  const r = classifyRow({ status: 'active', dw_sku: '', sku: 'DWAK-700042-Sample' }, new Set());
+  assert.equal(r.class, 'SELF_COPY_DW'); // DWAK is scraper-native, not a Phase-4 mint prefix
+  assert.equal(r.candidate, 'DWAK-700042');
+});
+
+test('PROVENANCE GUARD: greenfield set is configurable via opts', () => {
+  // Override the default set with a custom one — DWAK becomes guarded, DWAG does not.
+  const opts = { greenfieldPrefixes: new Set(['DWAK']) };
+  assert.equal(classifyRow({ status: 'active', dw_sku: '', sku: 'DWAK-1-Sample' }, new Set(), opts).class, 'MINT_RESIDUE_RESCRAPE');
+  assert.equal(classifyRow({ status: 'active', dw_sku: '', sku: 'DWAG-1-Sample' }, new Set(), opts).class, 'SELF_COPY_DW');
+});
+
 test('UNIT_SUFFIXES includes the observed vocabulary', () => {
   for (const w of ['Sample', 'Yard', 'Roll', 'Panel', 'Bolt', 'Per Yard']) {
     assert.ok(UNIT_SUFFIXES.includes(w), `${w} missing from UNIT_SUFFIXES`);

← abb32c7 TK-10896: read-only dw_sku backlog classifier + scanner + te  ·  back to Dw Sku Integrity  ·  TK-10896: record Claude-Codex no-mint comparison proof 04762e2 →