← back to Sample Followup Sweep

scripts/tk12118-vid-backfill.mjs

74 lines

// TK-12118 — backfill the missing `vid` on the 3 VERIFIED outstanding empty-vid records the
// coverage-reconcile canary (TK-12117) flagged as LEAKED (outstanding in FileMaker but dropped
// by the sweep before grouping because vid was blank → never chased, never reported).
//
// Vendors verified LIVE against FileMaker sibling records (not prefix guesses):
//   DWTT70793  rec 538622  entered 07/30/2026  -> vid "Thib" (Supplier "Thibaut Wallpaper"; sibling 02/18/2025 carries vid=Thib)
//   GLM9116    rec 541395  entered 09/08/2026  -> vid "FTR"  (Company "future Textile"; siblings carry vid=FTR = Future Textiles)
//   DWPN100994 rec 541415  entered 09/08/2026  -> vid "POI"  (Supplier "Pointe at Justin David"; sibling carries vid=POI)
//     NOTE: DWPN100994 ARRIVED 09/14/2026 on a twin record — after this backfill the never-false-chase
//     guard (TK-12105) will correctly SUPPRESS it (arrived), not chase it. Expected/correct.
//
// NOT included: DWLC1059 (Supplier "LA Walls") — LA Walls has NO vid anywhere in FileMaker or the
// fleet; assigning one is a vendor-registry decision for Steve. Left as WARN, not backfilled.
//
// USAGE:
//   node ~/Projects/sample-followup-sweep/scripts/tk12118-vid-backfill.mjs            # backfill + write restore-map
//   node ~/Projects/sample-followup-sweep/scripts/tk12118-vid-backfill.mjs --rollback # undo (set vid back to '')
// Blast radius = 3 rows. Reversible via the restore-map (data/tk12118-vid-backfill-restore-map.json).
import { readFileSync, writeFileSync } from 'node:fs';
import { homedir } from 'node:os';
import { join } from 'node:path';

const cfg = JSON.parse(readFileSync(join(homedir(), '.claude.json'), 'utf8'));
const env = cfg?.mcpServers?.filemaker?.env || {};
for (const k of ['FM_CLOUD_HOST', 'FM_CLARIS_EMAIL', 'FM_CLARIS_PASSWORD']) process.env[k] = env[k];
const fm = await import('/Users/macstudio3/Projects/filemaker-mcp/src/fm-client.js');

const DB = 'WALLPAPER', LAYOUT = 'Report for old memo samples', FIELD = 'vid';
// VERIFY layout: 'combo sku' does NOT serialize into fieldData on the write layout (findable but blank
// on read), so the SKU-match guard is verified on 'REPORT ON SAMPLES ORDERED' where it reads correctly
// (same base table WALLPAPER => same recordId space). The vid write still targets the write layout.
const VERIFY_LAYOUT = 'REPORT ON SAMPLES ORDERED';
const MAP = join(homedir(), 'Projects/sample-followup-sweep/data/tk12118-vid-backfill-restore-map.json');
const TARGETS = [
  { sku: 'DWTT70793',  recordId: '538622', vid: 'Thib', entered: '07/30/2026' },
  { sku: 'GLM9116',    recordId: '541395', vid: 'FTR',  entered: '09/08/2026' },
  { sku: 'DWPN100994', recordId: '541415', vid: 'POI',  entered: '09/08/2026' },
];
const ROLLBACK = process.argv.includes('--rollback');

// Read a record by its FileMaker recordId on the VERIFY layout (where 'combo sku' serializes).
async function readRec(recordId) {
  try { return await fm.getRecord(DB, VERIFY_LAYOUT, recordId); }
  catch { return null; }
}

if (ROLLBACK) {
  const map = JSON.parse(readFileSync(MAP, 'utf8'));
  for (const m of map) {
    await fm.updateRecord(DB, LAYOUT, m.recordId, { [FIELD]: m.oldVid }, { dryRun: false });
    console.log(`rolled back rec ${m.recordId} (${m.sku}) vid -> "${m.oldVid}"`);
  }
  console.log('rollback complete.');
  process.exit(0);
}

const restore = [];
for (const t of TARGETS) {
  // SAFETY: only write if the record still has an EMPTY vid and matches the expected SKU (guard against drift)
  const rec = await readRec(t.recordId);
  const cur = rec ? (rec.fieldData[FIELD] || '').trim() : null;
  const curSku = rec ? (rec.fieldData['combo sku'] || '').trim() : null;
  if (!rec) { console.log(`SKIP ${t.sku} rec ${t.recordId}: record not found`); continue; }
  if (curSku !== t.sku) { console.log(`SKIP ${t.sku} rec ${t.recordId}: SKU mismatch (found "${curSku}") — aborting this row`); continue; }
  if (cur !== '') { console.log(`SKIP ${t.sku} rec ${t.recordId}: vid already "${cur}" (not empty) — not overwriting`); continue; }
  restore.push({ sku: t.sku, recordId: t.recordId, oldVid: '' });
  await fm.updateRecord(DB, LAYOUT, t.recordId, { [FIELD]: t.vid }, { dryRun: false });
  // vid does not serialize into fieldData on either readable layout, so per-row read-back is impossible;
  // authoritative verification is the canary (coverage-reconcile.mjs) — leaked must drop by the write count.
  console.log(`OK   ${t.sku} rec ${t.recordId}: SKU verified on ${VERIFY_LAYOUT}, vid set -> "${t.vid}" (confirm via canary leaked-count)`);
}
writeFileSync(MAP, JSON.stringify(restore, null, 2));
console.log(`\nrestore-map written: ${MAP} (${restore.length} rows). Rollback: node scripts/tk12118-vid-backfill.mjs --rollback`);