← 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
M lib/wallpaper.jsM scripts/tk-10083-dryrun.mjs
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() + '