[object Object]

← back to Wallco Ai

refresh_designs_snapshot: add byte-size guard (refuse <85% of existing file — catches field-shrink clobber the count guard missed, the 06:30 blackout) + atomic write (tmp+os.replace, no partial-read /api/designs=0). Prevents recurring catalog blackout

12ce9a9db0e7b91228262287e170863308d1cfe3 · 2026-05-30 08:20:10 -0700 · Steve

Files touched

Diff

commit 12ce9a9db0e7b91228262287e170863308d1cfe3
Author: Steve <steve@designerwallcoverings.com>
Date:   Sat May 30 08:20:10 2026 -0700

    refresh_designs_snapshot: add byte-size guard (refuse <85% of existing file — catches field-shrink clobber the count guard missed, the 06:30 blackout) + atomic write (tmp+os.replace, no partial-read /api/designs=0). Prevents recurring catalog blackout
---
 scripts/refresh_designs_snapshot.py | 19 ++++++++++++++++++-
 1 file changed, 18 insertions(+), 1 deletion(-)

diff --git a/scripts/refresh_designs_snapshot.py b/scripts/refresh_designs_snapshot.py
index 628ddf9..2fa1c1a 100644
--- a/scripts/refresh_designs_snapshot.py
+++ b/scripts/refresh_designs_snapshot.py
@@ -201,7 +201,24 @@ if OUT.exists() and os.environ.get('WALLCO_FORCE_SNAPSHOT') != '1':
             pass
         raise SystemExit(0)
 
-OUT.write_text(json.dumps(out, indent=2))
+_new_blob = json.dumps(out, indent=2)
+# Byte-size guard — the COUNT guard above misses field-shrink clobbers: the
+# 2026-05-30 06:30 incident kept 34546/34551 designs but dropped 25.2MB->20.3MB
+# (fields stripped per row), so a count check passed while the catalog broke.
+# Refuse a materially smaller FILE unless explicitly forced.
+if OUT.exists() and os.environ.get('WALLCO_FORCE_SNAPSHOT') != '1':
+    _old_bytes = OUT.stat().st_size
+    if _old_bytes > 1000000 and len(_new_blob) < _old_bytes * 0.85:
+        print(f"ABORT: new designs.json {len(_new_blob)}B < 85%% of existing {_old_bytes}B "
+              f"(field-shrink clobber guard). Keeping existing. Override WALLCO_FORCE_SNAPSHOT=1.")
+        try: (OUT.parent / 'designs.json.rejected').write_text(_new_blob)
+        except Exception: pass
+        raise SystemExit(0)
+# Atomic write — tmp + os.replace so a concurrent /api/designs reload never reads
+# a partial file (the 06:30 ~4-min /api/designs=0 blackout was a mid-write read).
+_tmp = str(OUT) + '.tmp'
+open(_tmp, 'w').write(_new_blob)
+os.replace(_tmp, str(OUT))
 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}))}")
 print(f"  Top saturation 3: {[(x['title'], x['saturation']) for x in sorted(out, key=lambda x: -x['saturation'])[:3]]}")

← 39851c6 rooms: unified dual-path resolver (resolveRoomUrl/resolveDes  ·  back to Wallco Ai  ·  backfill-rooms-cron.sh: atomic designs.json write + catastro 0ed2c60 →