← back to Dw Add Sellable Variant Tk10902

tk10096/run-vahallan.mjs

114 lines

#!/usr/bin/env node
// TK-10096 — Vahallan FileMaker WALLPAPER master onboarding.
// Steve-approved 2026-09-01: 386 CREATE + 4 REPAIR + neutralize 4 dups (UPDATE, not delete).
// VERIFY-BEFORE-ACT: reads the 8 repair/dup records first, captures before-values for rollback.
// DRY by default; STEP=verify|repair|neutralize|create  APPLY=1 to commit.
import fs from 'node:fs';
import path from 'node:path';
import * as fm from '/Users/macstudio3/Projects/filemaker-mcp/src/fm-client.js';

const DIR = path.dirname(new URL(import.meta.url).pathname);
const DB = 'WALLPAPER';
const LAYOUT = 'Add wallcovering';
const APPLY = process.env.APPLY === '1';
const STEP = process.env.STEP || 'verify';
const ROLLBACK = path.join(DIR, 'rollback-values.json');
const LEDGER = `${process.env.HOME}/.claude/yolo-queue/executed-reversible/ledger.jsonl`;
const log = (o) => fs.appendFileSync(LEDGER, JSON.stringify(o) + '\n');
const now = () => new Date().toISOString();

// The 4 REPAIR survivors (keep, fix mfr_pattern + supplier/line=Vahallan)
const REPAIRS = [
  { recordId: '538683', mfrPattern: 'Cascada Chera' },
  { recordId: '538739', mfrPattern: 'Helicon Kaali' },
  { recordId: '540423', mfrPattern: 'Arcturus Bajada' },
  { recordId: '540422', mfrPattern: 'Impression Camille' },
];
// The 4 DUPS to NEUTRALIZE (blank Mfr Pattern + Width, stamp Internal Description)
const DUPS = ['538682', '538738', '540433', '540432'];

async function loadRecord(rid) {
  const r = await fm.getRecord(DB, LAYOUT, rid);
  return r ? { recordId: rid, fieldData: r.fieldData } : { recordId: rid, fieldData: null, missing: true };
}

async function verify() {
  const ids = [...REPAIRS.map(r => r.recordId), ...DUPS];
  const snap = {};
  for (const rid of ids) {
    const rec = await loadRecord(rid);
    snap[rid] = rec.fieldData;
    const fd = rec.fieldData || {};
    console.log(`${rid}: MfrPattern=${JSON.stringify(fd['Mfr Pattern'])} Width=${JSON.stringify(fd['Width'])} Supplier=${JSON.stringify(fd['Supplier'])} Line=${JSON.stringify(fd['Line'])} IntDesc=${JSON.stringify(fd['Internal Description'])}`);
  }
  fs.writeFileSync(ROLLBACK, JSON.stringify(snap, null, 2));
  console.log(`\nrollback-values.json written (${Object.keys(snap).length} records captured).`);
}

async function repair() {
  if (!fs.existsSync(ROLLBACK)) { console.error('run STEP=verify first to capture rollback values'); process.exit(1); }
  let ok = 0;
  for (const r of REPAIRS) {
    const res = await fm.updateRecord(DB, LAYOUT, r.recordId,
      { 'Mfr Pattern': r.mfrPattern, 'Supplier': 'Vahallan', 'Line': 'Vahallan' },
      { dryRun: !APPLY });
    console.log(`REPAIR ${r.recordId} -> "${r.mfrPattern}" committed=${res.committed} changes=${JSON.stringify(res.changes || {})}`);
    if (res.committed) ok++;
    await new Promise(s => setTimeout(s, 300));
  }
  if (APPLY) log({ ts: now(), agent: 'vp-dw-commerce', ticket: 'TK-10096',
    action: `REPAIR 4 Vahallan FM masters (538683 Cascada Chera / 538739 Helicon Kaali / 540423 Arcturus Bajada / 540422 Impression Camille; Supplier+Line=Vahallan)`,
    blast_radius: 4, undo_cmd: `restore Mfr Pattern/Supplier/Line from ${ROLLBACK}`, verify: `getRecord each -> correct Mfr Pattern + Supplier/Line=Vahallan` });
  console.log(`\nrepair ${APPLY ? 'COMMITTED' : 'DRY'}: ${ok}/${REPAIRS.length} committed`);
}

async function neutralize() {
  if (!fs.existsSync(ROLLBACK)) { console.error('run STEP=verify first'); process.exit(1); }
  let ok = 0;
  for (const rid of DUPS) {
    const res = await fm.updateRecord(DB, LAYOUT, rid,
      { 'Mfr Pattern': '', 'Width': '', 'Internal Description': 'TK-10096 dup — delete' },
      { dryRun: !APPLY });
    console.log(`NEUTRALIZE ${rid} committed=${res.committed} changes=${JSON.stringify(res.changes || {})}`);
    if (res.committed) ok++;
    await new Promise(s => setTimeout(s, 300));
  }
  if (APPLY) log({ ts: now(), agent: 'vp-dw-commerce', ticket: 'TK-10096',
    action: `NEUTRALIZE 4 Vahallan FM dup records (538682,538738,540433,540432): blank Mfr Pattern+Width, stamp Internal Description="TK-10096 dup — delete"`,
    blast_radius: 4, undo_cmd: `restore Mfr Pattern/Width/Internal Description from ${ROLLBACK}`, verify: `getRecord each -> Internal Description stamped` });
  console.log(`\nneutralize ${APPLY ? 'COMMITTED' : 'DRY'}: ${ok}/${DUPS.length} committed`);
}

async function create() {
  const set = JSON.parse(fs.readFileSync(path.join(DIR, 'create-set-386.json'), 'utf8'));
  let created = 0, skipped = 0, failed = 0, i = 0;
  const createdIds = [];
  const CREATED_LOG = path.join(DIR, 'created-ids.json');
  for (const it of set) {
    i++;
    // NOTE: 'combo sku' is a CALCULATED/auto-enter field (= Series & JS Pattern) — it CANNOT be
    // written on create (FileMaker [201] Field cannot be modified). Omit it; FM auto-computes it
    // to exactly combo_sku (verified: Series 'DWYG' + JS '292000' -> 'DWYG292000').
    const fieldData = {
      'Series': it.series, 'JS Pattern': it.js_pattern,
      'Mfr Pattern': it.mfr_pattern, 'Supplier': it.supplier, 'Line': it.line,
    };
    try {
      const res = await fm.createRecord(DB, LAYOUT, fieldData, { dryRun: !APPLY });
      if (res.committed) { created++; createdIds.push({ combo_sku: it.combo_sku, recordId: res.recordId }); }
      else if (res.skipped === 'duplicate' || res.wouldSkipDuplicate) skipped++;
      if (i % 25 === 0 || i === set.length) console.log(`[${i}/${set.length}] created=${created} skipped-dup=${skipped} failed=${failed}`);
    } catch (e) { failed++; console.error(`FAIL ${it.combo_sku}: ${String(e.message).slice(0, 120)}`); }
    if (APPLY) fs.writeFileSync(CREATED_LOG, JSON.stringify(createdIds, null, 2));
    await new Promise(s => setTimeout(s, 250));
  }
  if (APPLY) log({ ts: now(), agent: 'vp-dw-commerce', ticket: 'TK-10096',
    action: `CREATE ${created} Vahallan DWYG masters in FM WALLPAPER (of 386 in set; ${skipped} skipped as existing dups via Series+JS Pattern guard)`,
    blast_radius: created, undo_cmd: `delete created recordIds in ${CREATED_LOG}`, verify: `${CREATED_LOG} lists ${created} recordIds` });
  console.log(`\ncreate ${APPLY ? 'COMMITTED' : 'DRY'}: created=${created} skipped-dup=${skipped} failed=${failed} of ${set.length}`);
}

const steps = { verify, repair, neutralize, create };
if (!steps[STEP]) { console.error(`unknown STEP=${STEP}`); process.exit(1); }
await steps[STEP]();