[object Object]

← back to Dw Sku Integrity

TK-10896 fail closed on mixed SKU provenance

189503094317db04e8be38ac2ff7da7b30b18ab6 · 2026-08-30 11:26:25 -0700 · codex-10896

Files touched

Diff

commit 189503094317db04e8be38ac2ff7da7b30b18ab6
Author: codex-10896 <steve@designerwallcoverings.com>
Date:   Sun Aug 30 11:26:25 2026 -0700

    TK-10896 fail closed on mixed SKU provenance
---
 README.md                                          |  6 ++-
 classify.mjs                                       | 23 +++++++++-
 evidence/PROVENANCE-HOLD-2026-08-30.md             | 36 +++++++++++++++
 .../mac2-mirror-provenance-hold-2026-08-30.json    | 47 +++++++++++++++++++
 test/classify.test.mjs                             | 20 +++++++-
 verification/TK-10896-e2e-proof.json               | 51 +++++++++++++++++++++
 verification/provenance-plan.e2e.mjs               | 53 ++++++++++++++++++++++
 7 files changed, 231 insertions(+), 5 deletions(-)

diff --git a/README.md b/README.md
index bf745dd..60a83fd 100644
--- a/README.md
+++ b/README.md
@@ -23,7 +23,8 @@ existing `sku`.
   to no-mint — so the no-mint property holds *provided the guard catches all residue
   prefixes*. Greenfield (Bucket B) is fully covered; a small Bucket-A subset (≤~668
   rows minted into already-in-use prefixes) needs the exact reverted-mint ledger to
-  disambiguate and is flagged, not silently cleared (see `evidence/ANALYSIS`).
+  disambiguate. Those prefixes now fail closed into `PROVENANCE_REVIEW`; they are
+  neither silently cleared nor incorrectly sent to re-scrape (see `evidence/ANALYSIS`).
 - **dw_sku is intentionally non-unique.** A pattern's sellable + sample rows share one
   canonical `dw_sku`. The scanner reports `within_batch_shared_candidates` (benign
   same-pattern groups); an apply step MUST upsert, never assume per-row uniqueness.
@@ -34,7 +35,7 @@ existing `sku`.
 
 - `classify.mjs` — pure classifier: `stripUnitSuffix()`, `classifyRow()`, `RECOVERY_GROUP`.
 - `dwsku-backlog-scan.mjs` — read-only DB scanner → segmentation summary + plan JSONL.
-- `test/classify.test.mjs` — 20 unit tests (`node --test`).
+- `test/classify.test.mjs` — 27 unit tests (`node --test`).
 - `evidence/` — committed scan outputs + the analysis writeup.
 
 ## Usage
@@ -63,6 +64,7 @@ collision flag), suitable as the dry-run input to a *gated* apply step elsewhere
 | `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 |
+| `PROVENANCE_REVIEW` | code uses a mixed native/minted Phase-4 prefix (`DWKN/DWTT/DWRW/DWJS/DWRO/DWCC`) — exact undo ledger required | provenance_review_TK10896 |
 | `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 12e95d7..66e044e 100644
--- a/classify.mjs
+++ b/classify.mjs
@@ -53,6 +53,20 @@ export const GREENFIELD_MINT_PREFIXES = new Set([
   'DWWG', // Wolf Gordon
 ]);
 
+// Mixed-use Phase-4 prefixes. These prefixes predated the reverted mint, so a
+// prefix match alone cannot say whether a row is scraper-native or mint residue.
+// Fail closed into a dedicated provenance-review class until the retained
+// canonical undo ledger can identify the exact minted numbers. This deliberately
+// quarantines legitimate rows too; it never routes them to re-scrape or collision.
+export const MIXED_USE_MINT_PREFIXES = new Set([
+  'DWKN', // Knoll
+  'DWTT', // Thibaut
+  'DWRW', // Rebel Walls
+  'DWJS', // Jeffrey Stevens / York legacy shared prefix
+  'DWRO', // Romo
+  'DWCC', // Novasuede
+]);
+
 function greenfieldPrefixOf(code, prefixSet) {
   const m = /^(DW[A-Z0-9]{1,6})-/i.exec(code || '');
   if (!m) return null;
@@ -89,11 +103,12 @@ 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.
+ * @param {{greenfieldPrefixes?:Set<string>,mixedUsePrefixes?:Set<string>}} [opts] provenance-guard config.
  * @returns {{class:string, candidate:(string|null), collides:boolean, note?:string}}
  */
 export function classifyRow(row, activeCodeSet, opts = {}) {
   const greenfield = opts.greenfieldPrefixes || GREENFIELD_MINT_PREFIXES;
+  const mixedUse = opts.mixedUsePrefixes || MIXED_USE_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 };
@@ -118,6 +133,11 @@ export function classifyRow(row, activeCodeSet, opts = {}) {
       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);
+      const mixed = greenfieldPrefixOf(cand, mixedUse);
+      // A code already owned by another active product is a definite collision;
+      // that stronger fact wins over the unresolved mixed-prefix provenance hold.
+      if (mixed && collides) return { class: 'SELF_COPY_DW_COLLISION', candidate: cand, collides: true };
+      if (mixed) return { class: 'PROVENANCE_REVIEW', candidate: null, collides: false, note: `mixed-use Phase-4 prefix ${mixed} — compare ${cand} with retained undo ledger before self-copy` };
       return { class: collides ? 'SELF_COPY_DW_COLLISION' : 'SELF_COPY_DW', candidate: cand, collides };
     }
     if (CORK_RE.test(cand)) {
@@ -145,6 +165,7 @@ export const RECOVERY_GROUP = {
   RESCRAPE: 'rescrape_program_TK10900',
   IMPORT_DEFECT: 'rescrape_program_TK10900',
   MINT_RESIDUE_RESCRAPE: 'rescrape_program_TK10900',
+  PROVENANCE_REVIEW: 'provenance_review_TK10896',
   NOT_BLANK: 'out_of_scope',
   OUT_OF_SCOPE_STATUS: 'out_of_scope',
 };
diff --git a/evidence/PROVENANCE-HOLD-2026-08-30.md b/evidence/PROVENANCE-HOLD-2026-08-30.md
new file mode 100644
index 0000000..0bbc1b0
--- /dev/null
+++ b/evidence/PROVENANCE-HOLD-2026-08-30.md
@@ -0,0 +1,36 @@
+# TK-10896 — mixed-prefix provenance hold
+
+This phase closes the unsafe default identified by the prior analysis: rows in
+mixed-use Phase-4 prefixes were still routed to self-copy even though the exact
+reverted-mint numbers are not available on the Mac2 mirror.
+
+Canonical ticket evidence establishes that the retained undo source is
+`sku_repair_p4_20260826`, but a read-only `information_schema` lookup found no
+matching table in the local mirror. Canonical/Kamatera access was explicitly out
+of scope, so no remote lookup was attempted.
+
+The classifier now fails closed:
+
+- Greenfield mint prefixes remain `MINT_RESIDUE_RESCRAPE` with no candidate.
+- Mixed native/minted prefixes `DWKN/DWTT/DWRW/DWJS/DWRO/DWCC` become
+  `PROVENANCE_REVIEW` with no candidate until the undo ledger resolves them.
+- A candidate already used by another active product remains a definite collision;
+  that stronger fact routes to TK-10649 rather than being hidden by provenance review.
+- Other self-copy candidates must still be literal prefixes of the row's existing SKU.
+
+Fresh SELECT-only Mac2 mirror scan (`34,614` active blank rows):
+
+- `21,555` recoverable-now self-copy (down from the unsafe `25,623`)
+- `4,082` provenance review / exact-ledger required
+- `8,962` rescrape
+- `9` definite collision/dedup
+- `6` staging-link
+
+The scanner generated a 34,614-row local plan, the E2E verifier checked every row,
+then the plan was removed to avoid retaining a large identifier dump. The compact
+summary remains at `evidence/mac2-mirror-provenance-hold-2026-08-30.json`.
+
+No DB write, canonical/Kamatera access, mint, cleanup of user data, deploy, or
+Shopify action occurred. The safest next action is a separately authorized
+read-only export of the exact retained undo ledger, followed by a local join that
+releases only proven-native rows from `PROVENANCE_REVIEW`.
diff --git a/evidence/mac2-mirror-provenance-hold-2026-08-30.json b/evidence/mac2-mirror-provenance-hold-2026-08-30.json
new file mode 100644
index 0000000..3bca7b3
--- /dev/null
+++ b/evidence/mac2-mirror-provenance-hold-2026-08-30.json
@@ -0,0 +1,47 @@
+{
+  "generated_by": "dwsku-backlog-scan.mjs",
+  "ticket": "TK-10896",
+  "psql_prefix": "psql -h /tmp -d dw_unified",
+  "source_note": "Mac2 dw_unified mirror (NOT canonical — Kamatera owns shopify_products)",
+  "active_code_count": 55203,
+  "active_blank_dwsku": 34614,
+  "by_class": {
+    "SELF_COPY_DW": 18504,
+    "MINT_RESIDUE_RESCRAPE": 8897,
+    "PROVENANCE_REVIEW": 4082,
+    "SELF_COPY_SOURCE": 3026,
+    "RESCRAPE": 65,
+    "SELF_COPY_CORK": 25,
+    "STAGING_LINK": 6,
+    "SELF_COPY_COLLISION": 6,
+    "SELF_COPY_DW_COLLISION": 3
+  },
+  "by_recovery_group": {
+    "recoverable_now_self_copy": 21555,
+    "rescrape_program_TK10900": 8962,
+    "provenance_review_TK10896": 4082,
+    "dedup_gated_TK10649": 9,
+    "recoverable_now_staging_link": 6
+  },
+  "self_copy_provenance": {
+    "with_real_mfr_sku_cross_verifiable": 9147,
+    "sku_only_no_independent_check": 12408
+  },
+  "within_batch_shared_candidates": {
+    "note": "BENIGN: same-pattern sellable+sample rows resolving to one shared dw_sku (dw_sku is intentionally non-unique per pattern). These are NOT collisions; an apply step must upsert, not assume per-row uniqueness. True cross-product collisions are counted under *_COLLISION classes.",
+    "shared_candidate_groups": 1276,
+    "rows_in_shared_groups": 2552
+  },
+  "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 ec3cf74..f264b13 100644
--- a/test/classify.test.mjs
+++ b/test/classify.test.mjs
@@ -2,7 +2,7 @@
 // Run: node --test
 import { test } from 'node:test';
 import assert from 'node:assert/strict';
-import { stripUnitSuffix, classifyRow, UNIT_SUFFIXES, RECOVERY_GROUP } from '../classify.mjs';
+import { stripUnitSuffix, classifyRow, UNIT_SUFFIXES, RECOVERY_GROUP, MIXED_USE_MINT_PREFIXES } from '../classify.mjs';
 
 const ACTIVE = new Set(['DWKE-41415', 'DWCC-116000', 'DWCC-116200']); // simulated live codes
 
@@ -142,11 +142,27 @@ test('PROVENANCE GUARD: a NON-greenfield DW prefix stays SELF_COPY_DW', () => {
 
 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']) };
+  const opts = { greenfieldPrefixes: new Set(['DWAK']), mixedUsePrefixes: new Set() };
   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('PROVENANCE HOLD: every mixed-use Phase-4 prefix fails closed for ledger review', () => {
+  for (const prefix of MIXED_USE_MINT_PREFIXES) {
+    const r = classifyRow({ status: 'active', dw_sku: '', sku: `${prefix}-12345-Sample` }, new Set());
+    assert.equal(r.class, 'PROVENANCE_REVIEW', `${prefix} must not self-copy without the undo ledger`);
+    assert.equal(r.candidate, null);
+    assert.equal(r.collides, false);
+    assert.equal(RECOVERY_GROUP[r.class], 'provenance_review_TK10896');
+  }
+});
+
+test('PROVENANCE HOLD: mixed-use set is configurable and does not broaden silently', () => {
+  const opts = { mixedUsePrefixes: new Set(['DWAK']), greenfieldPrefixes: new Set() };
+  assert.equal(classifyRow({ status: 'active', dw_sku: '', sku: 'DWAK-1-Sample' }, new Set(), opts).class, 'PROVENANCE_REVIEW');
+  assert.equal(classifyRow({ status: 'active', dw_sku: '', sku: 'DWKN-1-Sample' }, new Set(), opts).class, 'SELF_COPY_DW');
+});
+
 test('BENIGN SHARED CODE: a pattern sellable + its -Sample resolve to the SAME candidate and both stay SELF_COPY_DW (not a collision)', () => {
   // Same pattern, two rows (sellable + sample). dw_sku is intentionally shared.
   const bare = classifyRow({ status: 'active', dw_sku: '', sku: 'DWPP-206465' }, new Set());
diff --git a/verification/TK-10896-e2e-proof.json b/verification/TK-10896-e2e-proof.json
new file mode 100644
index 0000000..eab10c3
--- /dev/null
+++ b/verification/TK-10896-e2e-proof.json
@@ -0,0 +1,51 @@
+{
+  "ticket": "TK-10896",
+  "intent": "Fail closed on mixed native/minted Phase-4 prefixes until exact undo-ledger provenance is available.",
+  "risk_tier": "R1 local classifier and SELECT-only mirror scan",
+  "environment": "Mac2 local repository and local dw_unified mirror; canonical Kamatera intentionally untouched",
+  "baseline_commit": "58d16de75fb6c10a5ca5ca3bde51319382c3fb3a",
+  "timestamp_utc": "2026-08-30T18:25:41Z",
+  "checks": [
+    {
+      "boundary": "unit",
+      "command": "npm test",
+      "assertion": "27/27 pass, including all six mixed-use prefixes, configurable guard sets, definite-collision precedence, and candidate-origin invariants",
+      "verdict": "PASS"
+    },
+    {
+      "boundary": "database read",
+      "command": "node dwsku-backlog-scan.mjs --out evidence/mac2-mirror-provenance-hold-2026-08-30.json --plan",
+      "assertion": "Scanner issued SELECT-only queries against the local mirror and classified 34,614 active blank rows",
+      "verdict": "PASS"
+    },
+    {
+      "boundary": "full dry-run plan",
+      "command": "node verification/provenance-plan.e2e.mjs evidence/mac2-mirror-provenance-hold-2026-08-30.plan.jsonl",
+      "assertions": [
+        "8,897 greenfield residue rows had no candidate and stayed in rescrape",
+        "4,082 mixed-use rows had no candidate and stayed in provenance review",
+        "2 mixed-use rows with independently definite active-code collisions stayed collision-gated",
+        "all 21,555 self-copy candidates were literal prefixes of their row SKU"
+      ],
+      "verdict": "PASS"
+    },
+    {
+      "boundary": "side effects",
+      "assertions": [
+        "no canonical or Kamatera access",
+        "no UPDATE, INSERT, DELETE, mint, Shopify call, deploy, or destructive cleanup",
+        "generated row-level plan removed after verification; compact summary retained"
+      ],
+      "verdict": "PASS"
+    },
+    {
+      "boundary": "diff integrity",
+      "command": "node --check classify.mjs && node --check dwsku-backlog-scan.mjs && git diff --check",
+      "verdict": "PASS"
+    }
+  ],
+  "critical_path_skips": [],
+  "scope_limit": "Exact canonical undo-ledger export is a future separately authorized read-only action; its absence blocks releasing provenance-review rows but does not block this local fail-closed guard.",
+  "cleanup": "The generated 34,614-row plan JSONL was deleted after all-row assertions; no database or external state was created.",
+  "overall": "PASS"
+}
diff --git a/verification/provenance-plan.e2e.mjs b/verification/provenance-plan.e2e.mjs
new file mode 100644
index 0000000..38ce419
--- /dev/null
+++ b/verification/provenance-plan.e2e.mjs
@@ -0,0 +1,53 @@
+#!/usr/bin/env node
+import { readFileSync } from 'node:fs';
+import { GREENFIELD_MINT_PREFIXES, MIXED_USE_MINT_PREFIXES } from '../classify.mjs';
+
+const planPath = process.argv[2];
+if (!planPath) throw new Error('usage: node verification/provenance-plan.e2e.mjs <plan.jsonl>');
+
+const rows = readFileSync(planPath, 'utf8').trim().split('\n').filter(Boolean).map(JSON.parse);
+const counts = {};
+let greenfield = 0;
+let mixedHeld = 0;
+let mixedCollision = 0;
+let selfCopy = 0;
+
+function prefixOf(sku) {
+  return (/^(DW[A-Z0-9]{1,6})-/i.exec(sku || '') || [])[1]?.toUpperCase() || null;
+}
+
+for (const row of rows) {
+  counts[row.class] = (counts[row.class] || 0) + 1;
+  const prefix = prefixOf(row.sku);
+
+  if (prefix && GREENFIELD_MINT_PREFIXES.has(prefix)) {
+    if (row.class !== 'MINT_RESIDUE_RESCRAPE' || row.candidate !== null) {
+      throw new Error(`greenfield row escaped no-mint guard: ${row.sku}`);
+    }
+    greenfield += 1;
+  }
+
+  if (prefix && MIXED_USE_MINT_PREFIXES.has(prefix)) {
+    if (row.class === 'PROVENANCE_REVIEW' && row.candidate === null) mixedHeld += 1;
+    else if (/COLLISION$/.test(row.class) && row.collides === true) mixedCollision += 1;
+    else throw new Error(`mixed-use row escaped provenance hold: ${row.sku} (${row.class})`);
+  }
+
+  if (row.group === 'recoverable_now_self_copy') {
+    if (!row.candidate || !String(row.sku).startsWith(row.candidate)) {
+      throw new Error(`self-copy candidate was not recovered from its row: ${row.sku}`);
+    }
+    selfCopy += 1;
+  }
+}
+
+const result = {
+  ok: true,
+  rows: rows.length,
+  greenfield_residue_held_for_rescrape: greenfield,
+  mixed_use_held_for_ledger_review: mixedHeld,
+  mixed_use_definite_collisions: mixedCollision,
+  self_copy_candidates_proven_from_row: selfCopy,
+  by_class: counts,
+};
+process.stdout.write(JSON.stringify(result, null, 2) + '\n');

← 58d16de Revert "TK-10896: record Claude-Codex no-mint comparison pro  ·  back to Dw Sku Integrity  ·  TK-10896 add exact ledger provenance join harness 9978a12 →