← back to Filemaker Mcp

scripts/tk-10083-dryrun.mjs

136 lines

#!/usr/bin/env node
// TK-10083 (iter 2) LIVE-PROOF DRY-RUN — combo-SKU master matching for OP-ART-DECO-WAVES.
//
// 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  (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 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).

import { existsSync, readFileSync } from 'node:fs';
import { fileURLToPath } from 'node:url';
import { dirname, join } from 'node:path';

const __dir = dirname(fileURLToPath(import.meta.url));
const ROOT = join(__dir, '..');

// --- 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_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 iter2 LIVE-PROOF master-match dry-run (READ-ONLY, NO WRITES) ===\n');

// ---- 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);
}

const MOD = await import(new URL('../lib/wallpaper.js', import.meta.url).href);
const { _internals } = MOD;
const { parseCombo, findExistingMaster, canonicalDashFor } = _internals;

// ---- 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);
}

// ---- [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 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);
}

// ---- [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 (live FileMaker read-only proof)' : failures + ' ASSERTION(S) FAILED'} ===`);
process.exit(failures === 0 ? 0 : 1);