← back to Mfr Review Viewer Corruption
scripts/mark-superseded.mjs
159 lines
#!/usr/bin/env node
// mark-superseded.mjs — the TK-10922 fallback remediation (DTD dissent, Steve-approved).
//
// FileMaker DELETE is structurally blocked by error [110] (a delete-time relationship
// resolution failure in the WALLPAPER graph). This does NOT delete. It stamps each broken
// placeholder master's existing free-text `Mfr Pattern` field with:
//
// SUPERSEDED - see master <keepRecordId>
//
// Why this and not "write the real code onto it": writing gz103 onto the placeholder would
// leave TWO records both carrying gz103 and make the duplicate-master ambiguity WORSE. Both
// the first-principles panelist and the contrarian flagged that trap independently.
//
// SAFETY RAILS (mirror execute-sweep.mjs):
// 1. DRY-RUN by default; nothing is written without --apply.
// 2. Snapshot-first: full record snapshotted to data/restore/ BEFORE any write; refuses
// to write without a snapshot on disk.
// 3. Execute-time re-verify: writes ONLY if the record STILL belongs to this dw_sku
// (skuMatch), STILL has no sample history, and its mfr is STILL a placeholder.
// 4. Keep-worthiness: the named keep must exist and be keep-worthy, else refuse.
// 5. Idempotent: a record already marked SUPERSEDED is skipped, not double-stamped.
// 6. Every write ledgered with an exact undo (restore the prior field value).
//
// Usage:
// node scripts/mark-superseded.mjs --plan data/plans/<plan>.json # DRY-RUN
// node scripts/mark-superseded.mjs --plan data/plans/<plan>.json --apply # EXECUTE
import { readFileSync, writeFileSync, existsSync, mkdirSync } from 'node:fs';
import { execFileSync } from 'node:child_process';
import path from 'node:path';
import os from 'node:os';
import { fileURLToPath } from 'node:url';
const __dir = path.dirname(fileURLToPath(import.meta.url));
const ROOT = path.join(__dir, '..');
const RESTORE_DIR = path.join(ROOT, 'data', 'restore');
const LOG_EXEC = path.join(os.homedir(), '.claude', 'yolo-queue', 'executed-reversible', 'log-exec.mjs');
const FM_PROJECT = path.join(os.homedir(), 'Projects', 'filemaker-mcp');
const FM_CLIENT = path.join(FM_PROJECT, 'src', 'fm-client.js');
const FM_DB = 'WALLPAPER';
const FM_LAYOUT = '*List Wallpapers - Full View';
const FM_WRITE_LAYOUT = process.env.FM_WRITE_LAYOUT || 'Basic List of Fields';
const arg = (k) => { const i = process.argv.indexOf('--' + k); return i >= 0 ? process.argv[i + 1] : undefined; };
const APPLY = process.argv.includes('--apply');
const PLAN_FILE = arg('plan');
const TICKET = arg('ticket') || 'TK-10922';
const AGENT = arg('agent') || process.env.TK_AGENT || 'claude-run-10922';
for (const line of readFileSync(path.join(FM_PROJECT, '.env'), 'utf8').split('\n')) {
const m = line.match(/^([A-Z_]+)=(.*)$/); if (m && !process.env[m[1]]) process.env[m[1]] = m[2];
}
let _fm = null;
const fm = async () => (_fm ||= await import('file://' + FM_CLIENT));
const numericTail = (s) => { const m = String(s || '').match(/(\d[\d]*)\s*$/); return m ? m[1] : ''; };
function parseNoteMfr(note) {
let t = String(note || '').trim(); if (!t) return '';
t = t.split(/[\r\n]/)[0].trim();
const hash = t.match(/#\s*([^\s;,-][^\s;,]*)/); if (hash) return hash[1].replace(/[.,]$/, '').trim();
const m = t.match(/^([^\s;]+?)(?:\s*;|\s+-\s+|\s+\$|\s+net\b|$)/i);
return m ? m[1].replace(/[.,]$/, '').trim() : t;
}
function classify(fd, dwSku) {
const clean = String(dwSku || '').trim().replace(/[-_ ]?sample$/i, '');
const tail = numericTail(clean);
const mfrPattern = String(fd['Mfr Pattern'] || '').trim();
const chase = String(fd['Vendor Sample - Where is Memo Send 2nd day'] || '').trim();
const noteMfr = parseNoteMfr(mfrPattern || (/#/.test(chase) ? chase : '')) || parseNoteMfr(chase);
const sampleOrdered = !!(String(fd['today for client'] || '').trim() || String(fd['Date WP Sample Sent'] || '').trim());
const codeDigits = String(noteMfr || mfrPattern || '').replace(/[^0-9]/g, '');
const mfrPlaceholder = !/[A-Za-z]/.test(noteMfr || '') && !!tail && codeDigits === tail;
const norm = (s) => String(s || '').toUpperCase().replace(/[^A-Z0-9]/g, '');
const target = norm(clean);
const skuMatch = !!target && (norm(String(fd.Series || '') + String(fd['JS Pattern'] || '')) === target || norm(fd['combo sku']) === target);
return { sampleOrdered, mfrPlaceholder, mfrPattern, noteMfr, skuMatch };
}
async function snapshot(rid, iso) {
const { getRecord } = await fm();
const rec = await getRecord(FM_DB, FM_LAYOUT, rid);
if (!rec) throw new Error(`record ${rid} not found`);
if (!existsSync(RESTORE_DIR)) mkdirSync(RESTORE_DIR, { recursive: true });
const f = path.join(RESTORE_DIR, `${rid}-${iso}.json`);
writeFileSync(f, JSON.stringify({ recordId: rec.recordId || rid, db: FM_DB, layout: FM_LAYOUT,
modId: rec.modId || null, captured_at: new Date().toISOString(), fieldData: rec.fieldData || {} }, null, 2));
if (!existsSync(f)) throw new Error(`snapshot write failed for ${rid}`);
return f;
}
function ledger(action, extra) {
try {
execFileSync('node', [LOG_EXEC, '--agent', AGENT, '--ticket', TICKET, '--action', action,
'--blast', String(extra.blast || 1), '--undo', extra.undo, '--verify', extra.verify], { stdio: 'pipe' });
} catch (e) { console.error(' ! ledger failed:', e.message); }
}
const MARK = /^SUPERSEDED\b/i;
async function main() {
if (!PLAN_FILE) { console.error('ERROR: --plan <file.json> required'); process.exit(2); }
const plan = JSON.parse(readFileSync(PLAN_FILE, 'utf8'));
console.log(`\n=== mark-superseded ${APPLY ? 'APPLY (LIVE canonical writes)' : 'DRY-RUN (no writes)'} ===`);
console.log(`plan : ${PLAN_FILE}`);
console.log(`writes : ${plan.reduce((n, e) => n + e.deleteRecordIds.length, 0)} field stamps ticket: ${TICKET}\n`);
const iso = new Date().toISOString().replace(/[:.]/g, '-');
let done = 0, skipped = 0, failed = 0;
const { getRecord, updateRecord } = await fm();
for (const e of plan) {
console.log(`--- ${e.dw_sku} ---`);
const keep = await getRecord(FM_DB, FM_LAYOUT, e.keepRecordId).catch(() => null);
if (!keep) { console.log(` GUARD: keep ${e.keepRecordId} not found live — refusing`); skipped += e.deleteRecordIds.length; continue; }
const kc = classify(keep.fieldData || {}, e.dw_sku);
if (!(kc.sampleOrdered || (!kc.mfrPlaceholder && /[A-Za-z]/.test(kc.noteMfr || kc.mfrPattern || '')))) {
console.log(` GUARD: keep ${e.keepRecordId} is NOT keep-worthy — refusing (inverted plan)`); skipped += e.deleteRecordIds.length; continue;
}
console.log(` keep ${e.keepRecordId} verified keep-worthy (mfr "${kc.mfrPattern}")`);
for (const rid of e.deleteRecordIds) {
const rec = await getRecord(FM_DB, FM_LAYOUT, rid).catch(() => null);
if (!rec) { console.log(` SKIP ${rid}: no longer exists`); skipped++; continue; }
const c = classify(rec.fieldData || {}, e.dw_sku);
const oldVal = String(rec.fieldData['Mfr Pattern'] || '');
if (MARK.test(oldVal)) { console.log(` SKIP ${rid}: already marked ("${oldVal}")`); skipped++; continue; }
if (!c.skuMatch) { console.log(` SKIP ${rid}: foreign record — does NOT belong to ${e.dw_sku}`); skipped++; continue; }
if (c.sampleOrdered) { console.log(` SKIP ${rid}: has SAMPLE history — this is a real master`); skipped++; continue; }
if (!c.mfrPlaceholder) { console.log(` SKIP ${rid}: mfr "${oldVal}" is not a placeholder`); skipped++; continue; }
const newVal = `SUPERSEDED - see master ${e.keepRecordId}`;
let snapFile;
try { snapFile = await snapshot(rid, iso); }
catch (err) { console.log(` ! SNAPSHOT FAILED ${rid} (${err.message}) — REFUSING to write`); failed++; continue; }
if (!APPLY) { console.log(` ${rid}: WOULD set Mfr Pattern "${oldVal}" -> "${newVal}" (snapshot ${path.relative(ROOT, snapFile)}) (dry-run)`); continue; }
try {
const r = await updateRecord(FM_DB, FM_WRITE_LAYOUT, rid, { 'Mfr Pattern': newVal }, { dryRun: false });
if (!r.committed) { console.log(` ${rid}: no change (${r.note || 'values matched'})`); skipped++; continue; }
const back = await getRecord(FM_DB, FM_LAYOUT, rid);
const now = String(back?.fieldData?.['Mfr Pattern'] || '');
if (now !== newVal) { console.log(` ! ${rid}: WRITE UNVERIFIED — reads back as "${now}"`); failed++; continue; }
console.log(` ${rid}: "${oldVal}" -> "${now}" [committed + verified]`);
done++;
ledger(`TK-10922 mark placeholder master ${rid} (${e.dw_sku}) SUPERSEDED -> keep ${e.keepRecordId}; Mfr Pattern "${oldVal}" -> "${newVal}"`, {
blast: 1,
undo: `cd ${ROOT} && node -e "import('file://${FM_CLIENT}').then(m=>m.updateRecord('WALLPAPER','${FM_WRITE_LAYOUT}','${rid}',{'Mfr Pattern':'${oldVal}'},{dryRun:false}))"`,
verify: `getRecord ${rid}."Mfr Pattern" === "${newVal}"`,
});
} catch (err) { console.log(` ! WRITE FAILED ${rid}: ${err.message}`); failed++; }
}
console.log('');
}
console.log('=== SUMMARY ===');
console.log(` stamped : ${done}${APPLY ? '' : ' (dry-run: 0 written)'}`);
console.log(` skipped : ${skipped}`);
console.log(` failed : ${failed}`);
console.log(APPLY ? '\n[APPLIED — live writes committed, verified + ledgered]' : '\n[DRY-RUN — nothing written. Add --apply.]');
}
main().catch((e) => { console.error('FATAL:', e.message); process.exit(1); });