[object Object]

← back to Wallco Ai

refresh_designs_snapshot: catastrophe guard against sparse-PG clobber

e91c2ad56adf8d24c9d6a8306479f44235ba3160 · 2026-05-26 10:39:56 -0700 · Steve Abrams

Root cause of the 2026-05-26 wallco.ai catalog collapse to 157 served:
server.js spawns this script on a recolor-bake (line ~19963). On PROD that
runs against prod's PG (only ~350 rows; the real 47k catalog lives in Mac2's
PG and ships via designs.json), regenerating designs.json down to ~285 items.
The server's 30s setInterval(load(true)) then reloaded the gutted file.

Guard: refuse to overwrite when the new snapshot is <50% of the existing one
(and existing >= 1000). Writes a designs.json.rejected sidecar for debugging.
Override with WALLCO_FORCE_SNAPSHOT=1. Protects regardless of where regen runs.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

Files touched

Diff

commit e91c2ad56adf8d24c9d6a8306479f44235ba3160
Author: Steve Abrams <steve@designerwallcoverings.com>
Date:   Tue May 26 10:39:56 2026 -0700

    refresh_designs_snapshot: catastrophe guard against sparse-PG clobber
    
    Root cause of the 2026-05-26 wallco.ai catalog collapse to 157 served:
    server.js spawns this script on a recolor-bake (line ~19963). On PROD that
    runs against prod's PG (only ~350 rows; the real 47k catalog lives in Mac2's
    PG and ships via designs.json), regenerating designs.json down to ~285 items.
    The server's 30s setInterval(load(true)) then reloaded the gutted file.
    
    Guard: refuse to overwrite when the new snapshot is <50% of the existing one
    (and existing >= 1000). Writes a designs.json.rejected sidecar for debugging.
    Override with WALLCO_FORCE_SNAPSHOT=1. Protects regardless of where regen runs.
    
    Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
---
 scripts/refresh_designs_snapshot.py | 26 ++++++++++++++++++++++++++
 1 file changed, 26 insertions(+)

diff --git a/scripts/refresh_designs_snapshot.py b/scripts/refresh_designs_snapshot.py
index 97eb287..7fe56d5 100644
--- a/scripts/refresh_designs_snapshot.py
+++ b/scripts/refresh_designs_snapshot.py
@@ -146,6 +146,32 @@ for r in rows:
         "unpublish_reason": extract_edges_tag(r.get('notes'))
     })
 
+# ── CATASTROPHE GUARD (2026-05-26) ────────────────────────────────────────
+# Never let a regen overwrite the catalog with a drastically smaller snapshot.
+# The real ~47k catalog lives in Mac2's PG and is shipped to prod via this
+# designs.json. PROD's own PG holds only ~350 rows, so any prod-side regen
+# trigger (e.g. the recolor-bake `spawn refresh_designs_snapshot.py` in
+# server.js) regenerates from the sparse prod PG and collapses designs.json to
+# ~285 items — which dropped the LIVE catalog to 157 served on 2026-05-26.
+# If an existing snapshot is materially larger than what we're about to write,
+# refuse and keep the good file. Override with WALLCO_FORCE_SNAPSHOT=1.
+if OUT.exists() and os.environ.get('WALLCO_FORCE_SNAPSHOT') != '1':
+    try:
+        _existing = json.loads(OUT.read_text())
+        _existing_n = len(_existing) if isinstance(_existing, list) else 0
+    except Exception:
+        _existing_n = 0
+    if _existing_n >= 1000 and len(out) < _existing_n * 0.5:
+        print(f"ABORT: refusing to shrink designs.json {_existing_n} → {len(out)} "
+              f"(<50% of existing). Almost certainly a sparse-PG regen "
+              f"(this PG has {len(rows)} rows). Keeping the existing snapshot. "
+              f"Set WALLCO_FORCE_SNAPSHOT=1 to override intentionally.")
+        try:
+            (OUT.parent / 'designs.json.rejected').write_text(json.dumps(out))
+        except Exception:
+            pass
+        raise SystemExit(0)
+
 OUT.write_text(json.dumps(out, indent=2))
 print(f"Wrote {len(out)} designs → {OUT}")
 print(f"  Categories: {dict((c, sum(1 for x in out if x['category']==c)) for c in sorted({x['category'] for x in out}))}")

← 39c5e38 geometric pilot: record ghost-detector false-fail verificati  ·  back to Wallco Ai  ·  snapshot: prefix-match generators to end recurring allowlist 4c2836c →