← back to Mfr Review Viewer Corruption
mfr-master-sweep executor: Cody-hardened — keep-in-delete guard, keep-worthiness guard (reject inverted plans), real runnable restore-record.mjs undo
a0b309dc4bb520bd106fed90533f82acdf6923d2 · 2026-08-27 09:26:49 -0700 · vp-dw-commerce
Files touched
M scripts/execute-sweep.mjsA scripts/restore-record.mjs
Diff
commit a0b309dc4bb520bd106fed90533f82acdf6923d2
Author: vp-dw-commerce <steve@designerwallcoverings.com>
Date: Thu Aug 27 09:26:49 2026 -0700
mfr-master-sweep executor: Cody-hardened — keep-in-delete guard, keep-worthiness guard (reject inverted plans), real runnable restore-record.mjs undo
---
scripts/execute-sweep.mjs | 38 ++++++++++++++++++++++++++--
scripts/restore-record.mjs | 63 ++++++++++++++++++++++++++++++++++++++++++++++
2 files changed, 99 insertions(+), 2 deletions(-)
diff --git a/scripts/execute-sweep.mjs b/scripts/execute-sweep.mjs
index 64025a1..459b0e3 100644
--- a/scripts/execute-sweep.mjs
+++ b/scripts/execute-sweep.mjs
@@ -200,10 +200,25 @@ async function main() {
const iso = new Date().toISOString().replace(/[:.]/g, '-');
let deleted = 0, mfrFixed = 0, skippedGuard = 0, skippedMismatch = 0, snapFail = 0;
- for (const e of plan) {
+ for (let e of plan) {
console.log(`--- ${e.dw_sku} ---`);
const { getRecord } = await fm();
+ // ---- SANITY GUARD (Cody hole #1): the keepRecordId must NEVER also be in the
+ // delete set. A malformed/stale plan that lists the same rid as keep AND delete
+ // would otherwise write mfr onto it then delete it. Drop the keep-rid from the
+ // delete list AND refuse the whole SKU (a plan this broken is not trustworthy). ----
+ const keepRid = String(e.keepRecordId || '');
+ const dedupedDeletes = [...new Set(e.deleteRecordIds.map(String))].filter((r) => r && r !== keepRid);
+ if (dedupedDeletes.length !== e.deleteRecordIds.length) {
+ console.log(` GUARD: plan listed keep=${keepRid} inside its own delete set (or dup rids) — DROPPING keep-rid + dups from deletes.`);
+ }
+ e = { ...e, deleteRecordIds: dedupedDeletes };
+ if (!e.deleteRecordIds.length) {
+ console.log(` (no deletes remain after keep/dup sanitation)`);
+ // still allow the KEEP mfr-write below to run; fall through
+ }
+
// Re-read every record in the plan (keep + deletes) to re-verify at execute time.
const allRids = [...new Set([e.keepRecordId, ...e.deleteRecordIds].filter(Boolean))];
const live = {};
@@ -212,6 +227,25 @@ async function main() {
catch { live[rid] = null; }
}
+ // ---- KEEP-worthiness guard (Cody hole #3): don't trust the plan blindly.
+ // The named keep record must ACTUALLY be keep-worthy (sample-ordered OR a real
+ // alpha mfr). If it's a placeholder-no-sample record, the plan is inverted — the
+ // "keep" is itself a broken master — so refuse ALL deletes for this SKU. ----
+ if (e.deleteRecordIds.length && live[keepRid]) {
+ const kc = classifyRecord(live[keepRid].fieldData || {}, e.dw_sku);
+ const keepWorthy = kc.sampleOrdered || (!kc.mfrPlaceholder && /[A-Za-z]/.test(kc.noteMfr || kc.mfrPattern || ''));
+ if (!keepWorthy) {
+ console.log(` GUARD: named keep ${keepRid} is NOT keep-worthy (no sample + placeholder mfr) — REFUSING all deletes AND the mfr-write for ${e.dw_sku} (inverted plan, untrusted).`);
+ skippedGuard += e.deleteRecordIds.length;
+ e = { ...e, deleteRecordIds: [], realMfr: '' }; // also don't trust the plan's realMfr
+ }
+ } else if (e.deleteRecordIds.length && !live[keepRid]) {
+ // keep record missing at execute time — cannot prove a survivor; refuse deletes.
+ console.log(` GUARD: named keep ${keepRid} not found live — cannot guarantee a survivor, REFUSING all deletes for ${e.dw_sku}.`);
+ skippedGuard += e.deleteRecordIds.length;
+ e = { ...e, deleteRecordIds: [] };
+ }
+
// never-delete-to-zero: how many records for this SKU will REMAIN?
// A record survives if it is NOT in the confirmed delete set (after re-verify).
const confirmedDeletes = [];
@@ -289,7 +323,7 @@ async function main() {
deleted++;
ledger(`mfr-master-sweep DELETE broken no-sample master ${rid} (${e.dw_sku})`, {
blast: 1,
- undo: `node -e "recreate WALLPAPER record from ${path.relative(ROOT, snap.file)} via fm createRecord --allowDuplicate"`,
+ undo: `cd ${ROOT} && node scripts/restore-record.mjs ${path.relative(ROOT, snap.file)} --apply`,
verify: `candidatesForSku('${e.dw_sku}') no longer lists rid ${rid}`,
});
} catch (err) { console.log(` ! DELETE failed for ${rid}: ${err.message}`); }
diff --git a/scripts/restore-record.mjs b/scripts/restore-record.mjs
new file mode 100644
index 0000000..b2d9bb6
--- /dev/null
+++ b/scripts/restore-record.mjs
@@ -0,0 +1,63 @@
+#!/usr/bin/env node
+// restore-record.mjs — REVERSE a delete performed by execute-sweep.mjs.
+//
+// Re-creates a deleted WALLPAPER master from its saved data/restore/<rid>-<ISO>.json
+// snapshot via the filemaker-mcp createRecord (allowDuplicate:true, since the sweep
+// intentionally removed a duplicate master — restoring re-adds that exact copy).
+//
+// This is the CONCRETE, runnable undo the ledger's undo_cmd points at — so a deleted
+// record is genuinely reversible, not just "we have a JSON somewhere".
+//
+// NOTE: FileMaker assigns a NEW recordId on re-create (the old internal id is gone).
+// Field DATA is fully restored; only the opaque recordId differs. Container/calc/
+// summary fields that FileMaker rejects on write are dropped with a warning.
+//
+// Usage:
+// node scripts/restore-record.mjs data/restore/391954-2026-08-27T16-17-13-048Z.json # DRY-RUN
+// node scripts/restore-record.mjs data/restore/391954-2026-08-27T16-17-13-048Z.json --apply # RE-CREATE
+
+import { readFileSync, existsSync } from 'node:fs';
+import path from 'node:path';
+import os from 'node:os';
+import { fileURLToPath } from 'node:url';
+
+const __dir = path.dirname(fileURLToPath(import.meta.url));
+const FM_PROJECT = path.join(os.homedir(), 'Projects', 'filemaker-mcp');
+const FM_ENV = path.join(FM_PROJECT, '.env');
+const FM_CLIENT = path.join(FM_PROJECT, 'src', 'fm-client.js');
+const FM_DB = 'WALLPAPER';
+const FM_LAYOUT = '*List Wallpapers - Full View';
+
+function loadFmEnv() {
+ if (!existsSync(FM_ENV)) return;
+ for (const line of readFileSync(FM_ENV, 'utf8').split('\n')) {
+ const m = line.match(/^([A-Z_]+)=(.*)$/);
+ if (m && !process.env[m[1]]) process.env[m[1]] = m[2];
+ }
+}
+loadFmEnv();
+
+const APPLY = process.argv.includes('--apply');
+const snapArg = process.argv.slice(2).find((a) => !a.startsWith('--'));
+if (!snapArg) { console.error('Usage: restore-record.mjs <snapshot.json> [--apply]'); process.exit(2); }
+
+// Fields FileMaker will reject on a write (calc / summary / container / global metadata).
+// We keep it conservative: strip nothing by name here, but let createRecord surface a
+// field error — if it does, re-run with an explicit --skip-fields list. Most Full-View
+// text/number fields write back fine.
+async function main() {
+ const snapPath = path.isAbsolute(snapArg) ? snapArg : path.join(__dir, '..', snapArg);
+ if (!existsSync(snapPath)) { console.error(`snapshot not found: ${snapPath}`); process.exit(2); }
+ const snap = JSON.parse(readFileSync(snapPath, 'utf8'));
+ const fieldData = snap.fieldData || {};
+ const rid = snap.recordId;
+ console.log(`restore ${rid} (${Object.keys(fieldData).length} fields) from ${path.basename(snapPath)} — ${APPLY ? 'APPLY' : 'DRY-RUN'}`);
+
+ const { createRecord } = await import('file://' + FM_CLIENT);
+ const r = await createRecord(FM_DB, FM_LAYOUT, fieldData, { dryRun: !APPLY, allowDuplicate: true });
+ if (r.committed) console.log(` RECREATED as new recordId ${r.recordId} (old id ${rid} is retired by FileMaker).`);
+ else console.log(` ${r.note || JSON.stringify(r)}`);
+ console.log(APPLY ? '\n[RESTORED]' : '\n[DRY-RUN — add --apply to re-create the record]');
+}
+
+main().catch((e) => { console.error('FATAL:', e.message); process.exit(1); });
← 23d36ff mfr-master-sweep: gated executor (dry-run default, snapshot-
·
back to Mfr Review Viewer Corruption
·
auto-data-snapshot: 2026-08-27T09:33:34 (3 data files) — dat df9f241 →