[object Object]

← back to Wallco Ai

generate_designs: committed read-back in persistDesign — phantom-insert fix

84ccb964c1029f7ee7bf91be9ae79f4826e2bf58 · 2026-06-04 08:13:08 -0700 · Steve Abrams

Root cause (6-agent workflow, high confidence): all_designs.id defaults to a
shared sequence (spoon_all_designs_id_seq). When an INSERT fails/rolls back
after nextval fires (settlement-trigger rejection or concurrent-generator
interleave), persistDesign() returned the burned id as success without
verifying the row committed — so batches reported phantom ids (PNG on disk,
no DB row), which looked like silent deletions. persistDesign now does a
fresh-connection SELECT read-back and throws if the row isn't visible; the
batch loop catch logs ✗ and continues, keeping the Created N/M count truthful.
Reversible: remove the read-back block.

Files touched

Diff

commit 84ccb964c1029f7ee7bf91be9ae79f4826e2bf58
Author: Steve Abrams <steve@designerwallcoverings.com>
Date:   Thu Jun 4 08:13:08 2026 -0700

    generate_designs: committed read-back in persistDesign — phantom-insert fix
    
    Root cause (6-agent workflow, high confidence): all_designs.id defaults to a
    shared sequence (spoon_all_designs_id_seq). When an INSERT fails/rolls back
    after nextval fires (settlement-trigger rejection or concurrent-generator
    interleave), persistDesign() returned the burned id as success without
    verifying the row committed — so batches reported phantom ids (PNG on disk,
    no DB row), which looked like silent deletions. persistDesign now does a
    fresh-connection SELECT read-back and throws if the row isn't visible; the
    batch loop catch logs ✗ and continues, keeping the Created N/M count truthful.
    Reversible: remove the read-back block.
---
 scripts/generate_designs.js | 20 +++++++++++++++++---
 1 file changed, 17 insertions(+), 3 deletions(-)

diff --git a/scripts/generate_designs.js b/scripts/generate_designs.js
index 7b8d86f..3f90722 100644
--- a/scripts/generate_designs.js
+++ b/scripts/generate_designs.js
@@ -730,11 +730,25 @@ VALUES (
 ) RETURNING id;`;
   const idStr = psql(sql);
   const newId = parseInt(idStr, 10);
+  if (!Number.isFinite(newId)) {
+    throw new Error('persistDesign: INSERT returned no id (insert failed or rolled back before RETURNING)');
+  }
+  // Committed read-back (2026-06-04 phantom-insert fix). all_designs.id defaults
+  // to nextval('spoon_all_designs_id_seq') — a value burned even when the INSERT
+  // later fails/rolls back (settlement-trigger rejection, or a concurrent
+  // generator interleaving on the shared sequence). Without this check, a phantom
+  // id whose row never committed was returned + logged as a ✓ success and pushed
+  // to created[], so the batch count lied and the "design" had only a PNG on disk
+  // and no DB row. Each psql() spawns a fresh connection, so this SELECT only sees
+  // the row if the INSERT actually committed — a phantom throws here instead, and
+  // main()'s per-design catch logs it + continues, keeping the count truthful.
+  const check = psql(`SELECT id FROM all_designs WHERE id=${newId};`).trim();
+  if (check !== String(newId)) {
+    throw new Error(`persistDesign: id ${newId} reported by RETURNING but NOT committed/visible — sequence burned, no row persisted`);
+  }
   // image_url can't be known until the row exists — set it to the by-id image
   // route so every grid / hero / search renderer has a working <img> src.
-  if (Number.isFinite(newId)) {
-    try { psql(`UPDATE spoon_all_designs SET image_url='/designs/img/by-id/${newId}' WHERE id=${newId};`); } catch {}
-  }
+  try { psql(`UPDATE spoon_all_designs SET image_url='/designs/img/by-id/${newId}' WHERE id=${newId};`); } catch {}
   return idStr;
 }
 

← 4e561f7 TODO: log staged Monterey/California-coast scenic mural batc  ·  back to Wallco Ai  ·  damask line batch-3: +3 heritage damasks live (noir-rose, sl 8537412 →