[object Object]

← back to Wallco Ai

generator: log gate rejections to data/logs/gate-rejections.jsonl (close orphan provenance gap)

dbe44646aea81d24fc9ed07a23b818db76dfc519 · 2026-06-09 13:25:24 -0700 · Steve Abrams

When the ghost/seamless/frame-overlay gate rejects a tile after its retry, the
loop continued WITHOUT a DB insert, leaving an orphan PNG with no recorded
reason. Now each skip appends {ts,seed,file,gate,reason,category,kind} so the
wallco-live-viewer can name the actual gate instead of inferring 'gate-rejected'
from absence-of-row. Append-only; logging failures never block generation.

Files touched

Diff

commit dbe44646aea81d24fc9ed07a23b818db76dfc519
Author: Steve Abrams <steve@designerwallcoverings.com>
Date:   Tue Jun 9 13:25:24 2026 -0700

    generator: log gate rejections to data/logs/gate-rejections.jsonl (close orphan provenance gap)
    
    When the ghost/seamless/frame-overlay gate rejects a tile after its retry, the
    loop continued WITHOUT a DB insert, leaving an orphan PNG with no recorded
    reason. Now each skip appends {ts,seed,file,gate,reason,category,kind} so the
    wallco-live-viewer can name the actual gate instead of inferring 'gate-rejected'
    from absence-of-row. Append-only; logging failures never block generation.
---
 scripts/generate_designs.js | 33 +++++++++++++++++++++++++++++++++
 1 file changed, 33 insertions(+)

diff --git a/scripts/generate_designs.js b/scripts/generate_designs.js
index 3f90722..35c14ff 100644
--- a/scripts/generate_designs.js
+++ b/scripts/generate_designs.js
@@ -28,6 +28,29 @@ fs.mkdirSync(OUT, { recursive: true });
 
 const BACKEND = process.env.GEN_BACKEND || 'stub';
 
+// ── Gate-rejection audit log (2026-06-09) ──────────────────────────────────
+// When a quality gate (ghost / seamless / frame-overlay) rejects a tile after
+// its retry, the loop `continue`s WITHOUT a DB insert — the PNG is orphaned on
+// disk with no provenance. Record WHICH gate killed it + the reason here, so the
+// wallco-live-viewer's "gate-rejected · no DB row" cards can name the actual gate
+// instead of inferring it from absence-of-row. Append-only JSONL keyed by the
+// same seed/filename the viewer reads. Logging failures never block generation.
+const GATE_REJECT_LOG = path.join(ROOT, 'data', 'logs', 'gate-rejections.jsonl');
+function logGateRejection({ seed, localPath, gate, reason, category, kind }) {
+  try {
+    fs.mkdirSync(path.dirname(GATE_REJECT_LOG), { recursive: true });
+    fs.appendFileSync(GATE_REJECT_LOG, JSON.stringify({
+      ts: new Date().toISOString(),
+      seed: seed != null ? String(seed) : null,
+      file: localPath ? path.basename(localPath) : null,
+      gate,
+      reason: reason || null,
+      category: category || null,
+      kind: kind || null,
+    }) + '\n');
+  } catch (e) { console.warn('  ⚠ gate-rejection log failed:', e.message); }
+}
+
 // ── Aesthetic direction (Steve 2026-05-19) ─────────────────────────────────
 // Tone-on-tone, high-fashion. Bright neon and >4-distinct-hue palettes are
 // purged at the catalog level (scripts/purge-neon-and-multicolor.js) AND
@@ -821,6 +844,9 @@ async function main() {
               const r2 = await verifyNoGhostLayer(outPath).catch(e => e);
               if (r2 && r2.code === 'GHOST_LAYER_DETECTED') {
                 console.warn(`  ✗ ghost-layer persists after retry — skipping #${seed}`);
+                logGateRejection({ seed, localPath: outPath, gate: 'ghost-layer',
+                  reason: (r2.detector_result && r2.detector_result.reason) || r2.message,
+                  category: opt.category, kind: opt.kind });
                 continue;
               }
               console.log(`  ✓ retry clean`);
@@ -874,6 +900,10 @@ async function main() {
               const r2 = await verifySeamless(outPath, { tolerance, category: opt.category }).catch(e => e);
               if (r2 && r2.code === 'SEAMLESS_DEFECT') {
                 console.warn(`  ✗ still not seamless after retry — skipping #${seed}`);
+                const rdr = r2.detector_result || {};
+                logGateRejection({ seed, localPath: outPath, gate: 'seamless-tile',
+                  reason: `${rdr.code || 'SEAMLESS_DEFECT'} tb=${rdr.top_bottom_diff} lr=${rdr.left_right_diff}`,
+                  category: opt.category, kind: opt.kind });
                 continue;
               }
               console.log(`  ✓ seamless after retry`);
@@ -911,6 +941,9 @@ async function main() {
               const r2 = await verifyNoFrameOverlay(outPath, { category: opt.category }).catch(e => e);
               if (r2 && r2.code === 'FRAME_OVERLAY_DETECTED') {
                 console.warn(`  ✗ frame-overlay persists after retry — skipping #${seed}`);
+                logGateRejection({ seed, localPath: outPath, gate: 'frame-overlay',
+                  reason: (r2.detector_result && r2.detector_result.reason) || r2.message,
+                  category: opt.category, kind: opt.kind });
                 continue;
               }
               console.log(`  ✓ frame-overlay clean after retry`);

← afa4976 cleanup: manifest of 14,632 May gate-rejected orphan designs  ·  back to Wallco Ai  ·  purge-orphan-pngs: reclaim gen-time orphan PNGs (no DB row) eedbd24 →