← 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]();