[object Object]

← back to Filemaker Mcp

TK-10083: fail-closed combo-SKU parser — stop duplicate WALLPAPER master mints

64cf597633322e98585cd189858d73f6db7b93b0 · 2026-08-03 10:34:51 -0700 · Steve

Root cause: combo sku is a stored FM calc (Series & JS Pattern, no separator);
the Series|remainder split is NOT derivable from the raw string, so onboarding
OP-ART-DECO-WAVES mis-split it (Series DWC) and minted duplicate masters
538697/538698 alongside the genuine 2019 master 240939.

Fix (proven vs LIVE FileMaker, read-only):
- normalizeSku + parseCombo.confident flag; all-alpha / unknown-prefix inputs
  are confident:false and CANNOT trigger a create (double guard: resolve + create).
- findExistingMaster now matches via component-field {Series,JS Pattern} OR-find
  over every dash-boundary split (calc == can't be reconstructed), with a
  normalized re-check that rejects the wrong-split DWC dupes.
- Real FM errors fail closed (existence unverifiable -> do not create).
- dryrun exits non-zero if FM creds absent (required live PASS).
Live proof: all 3 SKU forms -> master 240939, distinct=1, 0 duplicates.

Adversarially gated by Cody (contrarian): FIX-THEN-DRAFT -> SHIP, MUST-FIX empty.
Does NOT activate until the filemaker-mcp MCP restarts; duplicate cleanup of
538697/538698 is a SEPARATE gated action.

Files touched

Diff

commit 64cf597633322e98585cd189858d73f6db7b93b0
Author: Steve <steve@designerwallcoverings.com>
Date:   Mon Aug 3 10:34:51 2026 -0700

    TK-10083: fail-closed combo-SKU parser — stop duplicate WALLPAPER master mints
    
    Root cause: combo sku is a stored FM calc (Series & JS Pattern, no separator);
    the Series|remainder split is NOT derivable from the raw string, so onboarding
    OP-ART-DECO-WAVES mis-split it (Series DWC) and minted duplicate masters
    538697/538698 alongside the genuine 2019 master 240939.
    
    Fix (proven vs LIVE FileMaker, read-only):
    - normalizeSku + parseCombo.confident flag; all-alpha / unknown-prefix inputs
      are confident:false and CANNOT trigger a create (double guard: resolve + create).
    - findExistingMaster now matches via component-field {Series,JS Pattern} OR-find
      over every dash-boundary split (calc == can't be reconstructed), with a
      normalized re-check that rejects the wrong-split DWC dupes.
    - Real FM errors fail closed (existence unverifiable -> do not create).
    - dryrun exits non-zero if FM creds absent (required live PASS).
    Live proof: all 3 SKU forms -> master 240939, distinct=1, 0 duplicates.
    
    Adversarially gated by Cody (contrarian): FIX-THEN-DRAFT -> SHIP, MUST-FIX empty.
    Does NOT activate until the filemaker-mcp MCP restarts; duplicate cleanup of
    538697/538698 is a SEPARATE gated action.
---
 lib/wallpaper.js            | 161 ++++++++++++++++++++++++++++-------
 scripts/tk-10083-dryrun.mjs | 199 +++++++++++++++++++++++---------------------
 2 files changed, 233 insertions(+), 127 deletions(-)

diff --git a/lib/wallpaper.js b/lib/wallpaper.js
index f03b9fb..ee9d8ed 100644
--- a/lib/wallpaper.js
+++ b/lib/wallpaper.js
@@ -92,56 +92,113 @@ const SERIES_ALIASES = { CORK: ['DWGL', 'DWLG'], DWGL: ['CORK', 'DWLG'], DWLG: [
 // minted duplicates. 401=no match, 102=field not on layout, 105=layout, 106=table.
 const FM_SKIP_CODES = new Set(['401', '102', '105', '106']);
 
+// Enumerate every candidate {Series, JS Pattern} split of a dashed SKU form. The FM
+// master stores the SKU pre-split across two REAL, findable fields (Series + JS Pattern),
+// concatenated with NO separator into the `combo sku` calc. We cannot know the split from
+// the string (OP-ART|DECO-WAVES vs OP|ART-DECO-WAVES), so we generate a split at EVERY
+// separator boundary and let FileMaker's server-side component find pick the real one.
+//   "OP-ART-DECO-WAVES" -> {OP,ART-DECO-WAVES},{OP-ART,DECO-WAVES},{OP-ART-DECO,WAVES}
+// The normalized re-check on returned records rejects any wrong split that happens to hit.
+function splitCandidates(dashForm) {
+  const s = String(dashForm || '').trim();
+  const out = [];
+  const idxs = [];
+  for (let i = 0; i < s.length; i++) if (/[-_ ]/.test(s[i])) idxs.push(i);
+  for (const i of idxs) {
+    const series = s.slice(0, i);
+    const pattern = s.slice(i + 1);
+    if (series && pattern) out.push([series, pattern]);
+  }
+  return out;
+}
+
 // Alias- and error-aware existence check for a WALLPAPER master.
 // Returns { id, vid, err }: id/vid of the matching master (vid = its OWN stored code),
 // or err = a real (non-skip) FileMaker code meaning existence could not be verified.
 //
-// SEPARATOR-AGNOSTIC MATCH (TK-10083): the same SKU appears in THREE inconsistent forms
-// across the system — invoice OPARTDECOWAVES, FileMaker calc OP-ARTDECO-WAVES, raw
-// OP-ART-DECO-WAVES. A FileMaker "==" only matches the byte-for-byte stored calc, so a
-// single hard-coded query form silently misses the real master and ensureWallpaper mints
-// a DUPLICATE. Fix = query several candidate stored forms AND re-confirm every returned
-// record by comparing the NORMALIZED key on BOTH sides (normalizeSku(stored) ===
-// normalizeSku(target)) before accepting it, so all three forms resolve to one master.
-async function findExistingMaster(combo, p) {
+// SEPARATOR-AGNOSTIC MATCH (TK-10083, iter 2): the same SKU appears in THREE inconsistent
+// forms — invoice OPARTDECOWAVES, FileMaker calc OP-ARTDECO-WAVES, raw OP-ART-DECO-WAVES.
+// `combo sku` is a STORED CALC (= Series & JS Pattern, no separator); FileMaker returns
+// 401 on a wildcard find against it and a "==" matches ONLY the byte-for-byte stored
+// value — which we CANNOT reconstruct because the Series|JS-Pattern split is not derivable
+// from the string (proven live: ==OPARTDECOWAVES and ==OP-ART-DECO-WAVES both 401; only
+// the exact stored ==OP-ARTDECO-WAVES matches). Iter 1 relied on those calc "==" probes
+// and therefore silently missed the master -> minted a duplicate.
+//
+// The robust, server-side-correct path is the COMPONENT FIELDS: Series and JS Pattern are
+// real indexed text fields, findable with "==". We recover the canonical DASHED form from
+// dw_unified (Postgres normalizes all three forms back to the stored dw_sku, e.g.
+// OP-ART-DECO-WAVES), enumerate every dash-boundary split into {Series,JS Pattern}, issue
+// ONE _find with the OR-array of candidate splits, and accept a returned record only when
+// normalizeSku(Series + JS Pattern) === the target key. The OR-find matches whichever
+// split is real (here OP-ART|DECO-WAVES -> 240939); wrong splits match nothing.
+async function findExistingMaster(combo, p, dashCanonical) {
   const prefixes = [p.prefix, ...(SERIES_ALIASES[p.prefix] || [])].filter(Boolean);
   // Target normalized keys we accept a match against: the parsed key AND per-alias-prefix
   // rewrites (a renamed series stores a different prefix but the same pattern remainder).
   const targetKeys = new Set([p.key]);
   for (const pre of prefixes) targetKeys.add(normalizeSku(pre + p.num));
 
-  // Candidate STORED forms to probe with FileMaker "==". We cast a wide net (dashed and
-  // undashed, per prefix incl. aliases, plus the raw incoming string) precisely because
-  // we don't know which separator form this master was stored in; the normalized re-check
-  // below is what guarantees we only ACCEPT a genuine same-SKU record.
+  // ---- Pass A: cheap exact calc "==" probes (fast path for classic letters-then-digits
+  // SKUs whose stored calc form IS reconstructable). These are a best-effort accelerator;
+  // Pass B is the correctness guarantee. Only probe the field that exists (`combo sku`).
   const forms = new Set();
   const add = (v) => { if (v) forms.add(v); };
-  add(combo);            // raw incoming (e.g. OPARTDECOWAVES / OP-ART-DECO-WAVES)
-  add(p.dashSku);        // reconstructed dashed
-  add(p.raw);            // uppercased raw
+  add(combo); add(p.dashSku); add(p.raw);
+  if (dashCanonical) add(dashCanonical);
   for (const pre of prefixes) { add(pre + p.num); add(pre + '-' + p.num); }
-
-  // combo sku is the primary calc; comboskuwithdash is the dashed variant. Probe both.
-  const queries = [];
-  for (const v of forms) { queries.push({ 'combo sku': '==' + v }); queries.push({ comboskuwithdash: '==' + v }); }
-
-  for (const q of queries) {
+  for (const v of forms) {
     try {
-      const r = await fm.findRecords('WALLPAPER', FULL, q, { limit: 5 });
+      const r = await fm.findRecords('WALLPAPER', FULL, { 'combo sku': '==' + v }, { limit: 5 });
       for (const rec of (r.records || [])) {
         const fd = rec.fieldData || {};
-        // Normalize BOTH stored calc forms and accept only a true canonical-key match.
-        const storedKeys = [normalizeSku(fd['combo sku']), normalizeSku(fd.comboskuwithdash)];
-        if (storedKeys.some((k) => k && targetKeys.has(k))) {
+        if (targetKeys.has(normalizeSku(fd['combo sku']))) {
           return { id: rec.recordId, vid: (fd.vid || '').trim(), err: null };
         }
       }
     } catch (e) {
       const code = String(e.fmCode || '');
-      if (FM_SKIP_CODES.has(code)) continue;              // no-match / field not on layout -> next query
+      if (FM_SKIP_CODES.has(code)) continue;              // no-match / field not on layout -> next probe
       return { id: null, vid: '', err: code || e.message }; // real error -> UNVERIFIABLE
     }
   }
+
+  // ---- Pass B (correctness): component-field {Series==X, JS Pattern==Y} OR-find over
+  // every dash-boundary split of the canonical dashed form (from Postgres) AND the raw
+  // incoming string. This is the path that resolves OP-ART-DECO-WAVES -> 240939.
+  const splitSources = [];
+  if (dashCanonical) splitSources.push(dashCanonical);
+  splitSources.push(p.raw, p.dashSku, combo);
+  const seen = new Set();
+  const orQuery = [];
+  for (const src of splitSources) {
+    for (const [series, pattern] of splitCandidates(src)) {
+      const k = series.toUpperCase() + '' + pattern.toUpperCase();
+      if (seen.has(k)) continue;
+      seen.add(k);
+      orQuery.push({ Series: '==' + series, 'JS Pattern': '==' + pattern });
+    }
+  }
+  if (orQuery.length) {
+    try {
+      const r = await fm.findRecords('WALLPAPER', FULL, orQuery, { limit: 25 });
+      for (const rec of (r.records || [])) {
+        const fd = rec.fieldData || {};
+        // Re-confirm via the SAME canonical key the calc would produce: Series & JS Pattern
+        // concatenated (no separator), normalized. Accept only a genuine same-SKU record.
+        const storedKey = normalizeSku(String(fd.Series || '') + String(fd['JS Pattern'] || ''));
+        // Also accept if the stored calc itself normalizes to a target (belt-and-suspenders).
+        const comboKey = normalizeSku(fd['combo sku']);
+        if (targetKeys.has(storedKey) || targetKeys.has(comboKey)) {
+          return { id: rec.recordId, vid: (fd.vid || '').trim(), err: null };
+        }
+      }
+    } catch (e) {
+      const code = String(e.fmCode || '');
+      // 401 here means none of the candidate splits matched a master -> genuinely not found.
+      if (!FM_SKIP_CODES.has(code)) return { id: null, vid: '', err: code || e.message };
+    }
+  }
   return { id: null, vid: '', err: null };
 }
 
@@ -228,6 +285,21 @@ async function vidForSeries(series) {
   return best;
 }
 
+// Recover the CANONICAL DASHED dw_sku for any of the three SKU forms by matching on the
+// normalized key against Postgres (which stores the authoritative dashed form). Postgres
+// can normalize (regexp_replace) where FileMaker's calc "==" cannot, so this is what turns
+// invoice OPARTDECOWAVES / calc OP-ARTDECO-WAVES back into the stored OP-ART-DECO-WAVES —
+// the dashing that seeds the component-field OR-find's split candidates. Read-only.
+function canonicalDashFor(key) {
+  if (!key) return '';
+  const k = key.replace(/'/g, "''");
+  // NOTE: sql() JSON.parses its output, so SELECT a json object (not a bare text column).
+  const row = sql(`SELECT json_build_object('dw_sku', dw_sku) FROM shopify_products
+    WHERE upper(regexp_replace(regexp_replace(dw_sku,'-(SAMPLE)$','','i'),'[^A-Za-z0-9]','','g')) = '${k}'
+    ORDER BY (upper(regexp_replace(dw_sku,'[^A-Za-z0-9]','','g'))='${k}') DESC LIMIT 1`);
+  return (row && row.dw_sku) ? String(row.dw_sku) : '';
+}
+
 // READ-ONLY resolve — what WOULD be written for a combo sku, with NO FileMaker writes.
 // Returns { ok, mfr, vid, name, color, width, supplier, jpg, src } or { ok:false, reason }.
 // Used by ensureWallpaper (below) and by dry-run/preview callers.
@@ -235,12 +307,24 @@ export async function resolveWallpaperSource(combo) {
   const p = parseCombo(combo);
   if (!p) return { ok: false, flagged: combo, reason: 'unparseable sku' };
   const s = sourceFor(p.dashSku, p.key);
-  if (!s || !s.mfr) return { ok: false, flagged: combo, reason: `no mfr number (${s ? s.src : 'not in dw_unified'})` };
+  // Canonical dashed form (from Postgres) — used both to seed the component-field OR-find
+  // and, when the split is ambiguous, to gate whether a create is structurally allowed.
+  const dashCanonical = (s && s.canonical) ? String(s.canonical) : canonicalDashFor(p.key);
   // vid is the SKU's OWN vendor code. Prefer the existing WALLPAPER master's stored vid
   // (authoritative, per-SKU); fall back to the series-modal guess ONLY for a genuinely
-  // -new SKU. Previously vid was ALWAYS the series guess — which pushed the wrong (or a
-  // blank) VID onto invoices for products that already had a correct code on file.
-  const ex = await findExistingMaster(combo, p);
+  // -new SKU. The component-field OR-find (Pass B) is what resolves the non-derivable
+  // Series|JS-Pattern split (e.g. OP-ART-DECO-WAVES -> master 240939) that the calc "=="
+  // could never hit — this is the fix that stops the duplicate mint.
+  const ex = await findExistingMaster(combo, p, dashCanonical);
+  // FAIL CLOSED (TK-10083 iter 2): if NO existing master was found AND the Series|remainder
+  // split is not confidently derivable, we must NOT proceed to a create — a create off a
+  // guessed split is exactly what minted 538697/538698. Route to review instead. (If a
+  // master WAS found, we're only completing/reading it, so ambiguity is moot.)
+  if (!ex.id && !ex.err && !p.confident) {
+    return { ok: false, flagged: combo, reason: 'ambiguous-split-needs-review', _p: p, _existing: ex,
+      note: `Series|JS-Pattern split for "${combo}" is not derivable from the string and no existing master matched; not creating.` };
+  }
+  if (!s || !s.mfr) return { ok: false, flagged: combo, reason: `no mfr number (${s ? s.src : 'not in dw_unified'})`, _p: p, _existing: ex };
   const vid = ex.vid || await vidForSeries(p.prefix);
   const name = s.pattern || '', color = s.color || '', width = widthClean(s.width);
   const jpg = [name, color].filter(Boolean).join(' - ');
@@ -261,6 +345,19 @@ export async function ensureWallpaper(combo) {
   if (!ex.id && ex.err) return { ok: false, flagged: combo, reason: `existence unverified (${ex.err}) — not creating (would risk a duplicate)` };
   let id = ex.id;
   if (!id) {
+    // STRUCTURAL FAIL-CLOSED GUARDS (TK-10083 iter 2). A create is only reached for a
+    // genuinely-new SKU. Refuse it unless BOTH hold:
+    //  (1) the parse was CONFIDENT — the Series|remainder split is derivable, so we won't
+    //      mint off a guessed split (the 538697/538698 failure mode); and
+    //  (2) the Series to be written is non-blank — a Series:'' create is what a stray
+    //      all-alpha SKU would produce and is never valid.
+    // Either failing routes to review; a blank-Series create is now IMPOSSIBLE.
+    if (!p.confident) {
+      return { ok: false, flagged: combo, reason: 'ambiguous-split-needs-review — not creating (split not derivable)' };
+    }
+    if (!String(p.prefix || '').trim()) {
+      return { ok: false, flagged: combo, reason: 'blank-series — refusing to create a master with an empty Series' };
+    }
     const res = await fm.createRecord('WALLPAPER', ENTRY, { 'Mfr Pattern': s.mfr, 'JS Pattern': p.num, Series: p.prefix, Supplier: s.supplier || '', Width: width }, { dryRun: false }).catch((e) => ({ err: e.fmCode }));
     if (res.err) return { ok: false, flagged: combo, reason: `create failed ${res.err}` };
     id = res.recordId;
@@ -272,3 +369,7 @@ export async function ensureWallpaper(combo) {
   }
   return { ok: true, mfr: s.mfr, vid, name, color, width, supplier: s.supplier || '', src: r.src };
 }
+
+// Internal helpers exported for the TK-10083 live-proof dry-run so it exercises the ACTUAL
+// committed logic (not a re-implementation). findExistingMaster is READ-ONLY (only fm.find).
+export const _internals = { parseCombo, normalizeSku, findExistingMaster, canonicalDashFor, splitCandidates };
diff --git a/scripts/tk-10083-dryrun.mjs b/scripts/tk-10083-dryrun.mjs
index 8f97271..7632145 100644
--- a/scripts/tk-10083-dryrun.mjs
+++ b/scripts/tk-10083-dryrun.mjs
@@ -1,130 +1,135 @@
 #!/usr/bin/env node
-// TK-10083 DRY-RUN — combo-SKU parser + canonical normalizer fix for OP-ART-DECO-WAVES.
+// TK-10083 (iter 2) LIVE-PROOF DRY-RUN — combo-SKU master matching for OP-ART-DECO-WAVES.
 //
-// Proves the three inconsistent forms of the SAME SKU all parse and collapse to ONE
-// canonical key, so findExistingMaster resolves them to the SAME single master (record
-// 240939) instead of minting a duplicate.
+// Proves the corrected findExistingMaster resolves ALL THREE inconsistent forms of the
+// SAME SKU to the ONE genuine master (record 240939) via a REAL, read-only FileMaker
+// find — NOT a string simulation. Iteration 1 "passed" a pure-string simulation that
+// assumed the stored calc value was already in hand; that assumption is false (FileMaker's
+// calc "==" returns 401 for both the invoice and raw forms), which is why iter 1 still
+// minted a duplicate. This script exercises the ACTUAL committed findExistingMaster
+// against live FileMaker so the proof cannot lie.
 //
 //   invoice form : OPARTDECOWAVES     (no separators)
 //   FM calc form : OP-ARTDECO-WAVES   (master's stored `combo sku` calc)
-//   raw form     : OP-ART-DECO-WAVES
+//   raw form     : OP-ART-DECO-WAVES  (the form that minted the duplicate)
+//   genuine master = recordId 240939  (Series OP-ART, JS Pattern DECO-WAVES)
+//   duplicates (must NOT be returned) = 538697 / 538698 (Series DWC)
 //
-// HARD RAIL: this script performs NO FileMaker write. It runs FM in read-only mode
-// (FM_READONLY=1) and never calls ensureWallpaper's write path. The core assertion is a
-// PURE STRING check on the three literal forms and does not require FileMaker at all.
-// If live read-only FileMaker access IS available, it additionally confirms all three
-// forms resolve to the SAME single master recordId (bonus).
+// HARD RAILS:
+//   * FM_READONLY=1 is forced — the fm-client write guard throws on any create/update.
+//   * Only fm.findRecords (GET/_find) is called; NO create/update/delete path is touched.
+//   * REQUIRED live proof: if FileMaker creds are absent OR FileMaker is unreachable, the
+//     script EXITS NON-ZERO (this is a required PASS, not a silent skip).
 
-process.env.FM_READONLY = '1'; // belt-and-suspenders: block any accidental write path
-
-const MOD = new URL('../lib/wallpaper.js', import.meta.url);
+import { existsSync, readFileSync } from 'node:fs';
+import { fileURLToPath } from 'node:url';
+import { dirname, join } from 'node:path';
 
-// The parser + normalizer aren't exported, so re-derive them from the module source to
-// exercise the ACTUAL committed logic without duplicating it. We import the module for
-// its exported resolve path, and re-implement the two pure helpers here ONLY to assert
-// the canonical-key invariant deterministically (they mirror lib/wallpaper.js exactly).
-const normalizeSku = (s) => String(s || '').toUpperCase().replace(/[^A-Z0-9]/g, '');
+const __dir = dirname(fileURLToPath(import.meta.url));
+const ROOT = join(__dir, '..');
 
-function parseCombo(combo) {
-  const raw = String(combo || '').trim();
-  if (!raw) return null;
-  let prefix, num, m;
-  if ((m = raw.match(/^([A-Za-z]+)[-_ ](.+)$/))) { prefix = m[1]; num = m[2]; }
-  else if ((m = raw.match(/^([A-Za-z]+)(\d.*)$/))) { prefix = m[1]; num = m[2]; }
-  else { prefix = ''; num = raw; }
-  prefix = prefix.toUpperCase();
-  const dashSku = prefix ? `${prefix}-${num}` : num;
-  return { prefix, num, dashSku, raw: raw.toUpperCase(), key: normalizeSku(raw) };
+// --- load .env (the connector reads Cognito creds + FM_CLOUD_HOST from it) ---
+const envPath = join(ROOT, '.env');
+if (existsSync(envPath)) {
+  for (const line of readFileSync(envPath, 'utf8').split('\n')) {
+    const m = line.match(/^\s*([A-Z0-9_]+)\s*=\s*(.*)\s*$/);
+    if (m && !(m[1] in process.env)) {
+      let v = m[2];
+      if ((v.startsWith('"') && v.endsWith('"')) || (v.startsWith("'") && v.endsWith("'"))) v = v.slice(1, -1);
+      process.env[m[1]] = v;
+    }
+  }
 }
 
+process.env.FM_READONLY = '1'; // belt-and-suspenders: block any accidental write path
+
 const FORMS = {
   invoice: 'OPARTDECOWAVES',
   fm_calc: 'OP-ARTDECO-WAVES',
   raw: 'OP-ART-DECO-WAVES',
 };
-const EXPECTED_KEY = 'OPARTDECOWAVES';
+const EXPECTED_MASTER = '240939';
+const DUPLICATES = new Set(['538697', '538698']);
 
 let failures = 0;
 const assert = (cond, msg) => { if (!cond) { failures++; console.log(`  ✗ FAIL: ${msg}`); } else { console.log(`  ✓ ${msg}`); } };
 
-console.log('=== TK-10083 combo-SKU normalizer dry-run (NO WRITES) ===\n');
+console.log('=== TK-10083 iter2 LIVE-PROOF master-match dry-run (READ-ONLY, NO WRITES) ===\n');
 
-// ---- 1) CORE PURE-STRING ASSERTIONS (no FileMaker needed) ----
-console.log('[1] parseCombo + normalizeSku on the three literal forms:');
-const parsed = {};
-for (const [label, form] of Object.entries(FORMS)) {
-  const p = parseCombo(form);
-  parsed[label] = p;
-  console.log(`    ${label.padEnd(8)} "${form}" -> parsed=${p ? 'OK' : 'null'} key="${p ? p.key : ''}"`);
-  assert(p !== null, `${label} form parses (was null under old letters-then-digits regex)`);
-  assert(p && p.key === EXPECTED_KEY, `${label} form -> canonical key "${EXPECTED_KEY}"`);
+// ---- REQUIRED: FileMaker creds must be present. Absent => non-zero exit (not a skip). ----
+const haveCreds = !!(process.env.FM_CLOUD_HOST && (process.env.FM_CLARIS_EMAIL || process.env.FM_CLOUD_USER) && (process.env.FM_CLARIS_PASSWORD || process.env.FM_CLOUD_PASSWORD));
+if (!haveCreds) {
+  console.log('  ✗ FAIL: FileMaker creds (FM_CLOUD_HOST + FM_CLARIS_EMAIL/PASSWORD) are ABSENT.');
+  console.log('           Live verification is REQUIRED for this fix — refusing to pass silently.');
+  console.log('\n=== RESULT: 1 ASSERTION FAILED (creds absent; live proof could not run) ===');
+  process.exit(1);
 }
 
-console.log('\n[2] All three canonical keys are identical:');
-const keys = Object.values(parsed).map((p) => p.key);
-const uniq = [...new Set(keys)];
-console.log(`    keys = ${JSON.stringify(keys)} ; distinct = ${uniq.length}`);
-assert(uniq.length === 1, 'all three forms collapse to exactly ONE canonical key');
-assert(uniq[0] === EXPECTED_KEY, `the single key is "${EXPECTED_KEY}"`);
+const MOD = await import(new URL('../lib/wallpaper.js', import.meta.url).href);
+const { _internals } = MOD;
+const { parseCombo, findExistingMaster, canonicalDashFor } = _internals;
 
-console.log('\n[3] Normalizer does NOT over-collapse legitimately-distinct SKUs:');
-const distinctCases = [
-  ['SCH-12345', 'SCH12345'],
-  ['SCH-12346', 'SCH12346'],
-  ['OP-ART-DECO-WAVE', 'OPARTDECOWAVE'],   // singular, one char off -> must differ
-  ['DWKK134979', 'DWKK134979'],
-];
-for (const [inp, exp] of distinctCases) {
-  const got = normalizeSku(inp);
-  assert(got === exp, `normalizeSku("${inp}") = "${got}" (digits preserved / distinct kept)`);
+// ---- Verify live reachability up front; unreachable => non-zero exit (not a skip). ----
+const fm = await import(new URL('../src/fm-client.js', import.meta.url).href);
+try {
+  await fm.ping();
+  console.log('  ✓ FileMaker Cloud reachable (Cognito auth + Data API session OK)\n');
+} catch (e) {
+  console.log(`  ✗ FAIL: FileMaker unreachable — live proof REQUIRED. (${e.message})`);
+  console.log('\n=== RESULT: 1 ASSERTION FAILED (FM unreachable; live proof could not run) ===');
+  process.exit(1);
 }
-assert(normalizeSku('SCH-12345') !== normalizeSku('SCH-12346'), 'SCH-12345 and SCH-12346 stay DISTINCT');
-assert(normalizeSku(FORMS.raw) !== normalizeSku('OP-ART-DECO-WAVE'), 'WAVES and WAVE stay DISTINCT (no over-collapse)');
 
-// ---- 4) DUPLICATE-COUNT INVARIANT (pure-string simulation) ----
-// Simulate findExistingMaster's matching decision against a single known stored master
-// whose `combo sku` calc holds the FM-calc form. All three incoming forms must NORMALIZE-
-// match that one stored value -> 1 master matched, 0 duplicates minted.
-console.log('\n[4] Duplicate-mint simulation vs the single stored master (calc="OP-ARTDECO-WAVES"):');
-const STORED_MASTER = { recordId: '240939', comboSkuCalc: 'OP-ARTDECO-WAVES' };
-const storedKey = normalizeSku(STORED_MASTER.comboSkuCalc);
-let mastersMatched = new Set();
-let dupWouldMint = 0;
+// ---- [1] LIVE: each of the three forms resolves to the genuine master 240939 ----
+console.log('[1] LIVE findExistingMaster() — each form must return master ' + EXPECTED_MASTER + ':');
+const matchedIds = [];
 for (const [label, form] of Object.entries(FORMS)) {
   const p = parseCombo(form);
-  const matches = p && p.key === storedKey;
-  if (matches) mastersMatched.add(STORED_MASTER.recordId);
-  else dupWouldMint++;
-  console.log(`    ${label.padEnd(8)} key="${p.key}" vs stored="${storedKey}" -> ${matches ? 'MATCH master ' + STORED_MASTER.recordId : 'NO MATCH -> would mint duplicate'}`);
+  const dash = canonicalDashFor(p.key);
+  let ex;
+  try {
+    ex = await findExistingMaster(form, p, dash);
+  } catch (e) {
+    failures++; console.log(`    ✗ ${label.padEnd(8)} "${form}" -> THREW ${e.message}`);
+    continue;
+  }
+  console.log(`    ${label.padEnd(8)} "${form}"  confident=${p.confident}  canonicalDash="${dash}"  -> id=${ex.id ?? 'null'} vid="${ex.vid || ''}" err=${ex.err ?? 'null'}`);
+  assert(ex.id === EXPECTED_MASTER, `${label} form resolves to master ${EXPECTED_MASTER} (not a duplicate, not null)`);
+  assert(!DUPLICATES.has(String(ex.id)), `${label} form did NOT return a duplicate (538697/538698)`);
+  if (ex.id) matchedIds.push(ex.id);
 }
-assert(mastersMatched.size === 1, 'all three forms match exactly ONE master (record 240939)');
-assert(dupWouldMint === 0, '0 duplicate masters would be minted');
 
-// ---- 5) BONUS: live read-only FileMaker resolve (skipped gracefully if no creds) ----
-console.log('\n[5] BONUS live read-only resolve (skipped if FileMaker creds absent):');
-try {
-  const { resolveWallpaperSource } = await import(MOD.href);
-  const results = {};
-  let fmReachable = true;
-  for (const [label, form] of Object.entries(FORMS)) {
-    let r;
-    try { r = await resolveWallpaperSource(form); }
-    catch (e) { fmReachable = false; console.log(`    ${label}: FM unreachable (${e.message})`); break; }
-    results[label] = r;
-    const exId = r._existing ? r._existing.id : null;
-    console.log(`    ${label.padEnd(8)} ok=${r.ok} existingMasterId=${exId ?? 'null'} vid="${r.vid || ''}" reason="${r.reason || ''}"`);
-  }
-  if (fmReachable && Object.keys(results).length === 3) {
-    const ids = Object.values(results).map((r) => (r._existing ? r._existing.id : null)).filter(Boolean);
-    const uid = [...new Set(ids)];
-    console.log(`    matched master ids = ${JSON.stringify(ids)} ; distinct = ${uid.length}`);
-    assert(uid.length <= 1, 'live: the three forms resolve to at most ONE master (0 duplicates)');
-  } else {
-    console.log('    (live read-only FileMaker not available in this env — core string assertions above already prove the fix)');
-  }
-} catch (e) {
-  console.log(`    (bonus live check unavailable: ${e.message} — core string assertions above already prove the fix)`);
+// ---- [2] All three collapse to exactly ONE master id (0 duplicates would be minted) ----
+console.log('\n[2] All three forms resolve to exactly ONE master (0 duplicates minted):');
+const uniq = [...new Set(matchedIds)];
+console.log(`    matched ids = ${JSON.stringify(matchedIds)} ; distinct = ${uniq.length}`);
+assert(matchedIds.length === 3, 'all three forms returned a master (none fell through to create)');
+assert(uniq.length === 1 && uniq[0] === EXPECTED_MASTER, `the single distinct master is ${EXPECTED_MASTER}`);
+
+// ---- [3] resolveWallpaperSource returns the SAME single existing master (no create path) ----
+console.log('\n[3] LIVE resolveWallpaperSource() — reuses the existing master, never creates:');
+const { resolveWallpaperSource } = MOD;
+for (const [label, form] of Object.entries(FORMS)) {
+  const r = await resolveWallpaperSource(form);
+  const exId = r._existing ? r._existing.id : null;
+  console.log(`    ${label.padEnd(8)} ok=${r.ok} existingMasterId=${exId ?? 'null'} reason="${r.reason || ''}"`);
+  assert(exId === EXPECTED_MASTER, `${label}: resolveWallpaperSource sees existing master ${EXPECTED_MASTER}`);
+}
+
+// ---- [4] FAIL-CLOSED: a genuinely-ambiguous, non-existent SKU never reaches create ----
+// A made-up all-alpha SKU with no master and no confident split must return needs-review.
+console.log('\n[4] Fail-closed guard — ambiguous, non-existent SKU routes to review (no create):');
+const GHOST = 'ZZQWXNONEXISTENTPATTERN';
+{
+  const p = parseCombo(GHOST);
+  console.log(`    parseCombo("${GHOST}") -> confident=${p.confident} prefix="${p.prefix}"`);
+  assert(p.confident === false, 'ghost all-alpha SKU parses as NOT confident');
+  const r = await resolveWallpaperSource(GHOST);
+  console.log(`    resolveWallpaperSource -> ok=${r.ok} reason="${r.reason || ''}"`);
+  assert(r.ok === false, 'ghost SKU resolve fails (does not proceed toward a create)');
+  const okReason = /ambiguous-split-needs-review|no mfr number/.test(r.reason || '');
+  assert(okReason, 'ghost SKU reason is a review/skip reason, not a create');
 }
 
-console.log(`\n=== RESULT: ${failures === 0 ? 'ALL ASSERTIONS PASSED' : failures + ' ASSERTION(S) FAILED'} ===`);
+console.log(`\n=== RESULT: ${failures === 0 ? 'ALL ASSERTIONS PASSED (live FileMaker read-only proof)' : failures + ' ASSERTION(S) FAILED'} ===`);
 process.exit(failures === 0 ? 0 : 1);

← f72e424 auto-save: 2026-08-03T10:23:02 (2 files) — lib/wallpaper.js  ·  back to Filemaker Mcp  ·  auto-save: 2026-08-03T11:23:33 (1 files) — scripts/tk-10083- 9c6b447 →