← back to Wallco Ai
fix(publish-gate): sudo-psql on Linux + image-path fallback — gate falsely failed on prod
cf75a5c255194229ef1f08c774837c6351443ae7 · 2026-05-29 12:46:12 -0700 · Steve Abrams
Two infra false-fails made the publish gate park EVERY design on prod (never a
real quality verdict):
1. psql() ran bare 'psql dw_unified' as the current user. On prod pm2 runs node
(and the gate) as root, but PG has no root role → empty result → gate
reported 'design not found' and parked the publish. Now uses
'sudo -n -u postgres psql' on Linux, matching lib/db.js PSQL_CMD.
2. Image resolution only tried local_path (a Mac2-style /Users/... path that
doesn't exist on prod) → 'image file missing/unreadable'. Now falls back to
data/generated/<basename(local_path|image_url)> + quarantine dirs, same chain
as the by-id handler.
Surfaced when bulk-publishing colorways-imperial-garden-barone: '0 published,
5 parked (gate)' though the designs render fine. No quality logic changed.
Files touched
M scripts/publish-gate.py
Diff
commit cf75a5c255194229ef1f08c774837c6351443ae7
Author: Steve Abrams <steve@designerwallcoverings.com>
Date: Fri May 29 12:46:12 2026 -0700
fix(publish-gate): sudo-psql on Linux + image-path fallback — gate falsely failed on prod
Two infra false-fails made the publish gate park EVERY design on prod (never a
real quality verdict):
1. psql() ran bare 'psql dw_unified' as the current user. On prod pm2 runs node
(and the gate) as root, but PG has no root role → empty result → gate
reported 'design not found' and parked the publish. Now uses
'sudo -n -u postgres psql' on Linux, matching lib/db.js PSQL_CMD.
2. Image resolution only tried local_path (a Mac2-style /Users/... path that
doesn't exist on prod) → 'image file missing/unreadable'. Now falls back to
data/generated/<basename(local_path|image_url)> + quarantine dirs, same chain
as the by-id handler.
Surfaced when bulk-publishing colorways-imperial-garden-barone: '0 published,
5 parked (gate)' though the designs render fine. No quality logic changed.
---
scripts/publish-gate.py | 42 +++++++++++++++++++++++++++++++++++-------
1 file changed, 35 insertions(+), 7 deletions(-)
diff --git a/scripts/publish-gate.py b/scripts/publish-gate.py
index 7641f60..bdd5224 100644
--- a/scripts/publish-gate.py
+++ b/scripts/publish-gate.py
@@ -33,22 +33,50 @@ VMODEL = os.environ.get('OLLAMA_VISION_MODEL', 'llava:latest')
_spec = importlib.util.spec_from_file_location('es', str(ROOT / 'scripts' / 'edges-scan.py'))
es = importlib.util.module_from_spec(_spec); _spec.loader.exec_module(es)
+# Mirror lib/db.js PSQL_CMD: on Linux (Kamatera) pm2 runs node — and thus this
+# gate — as root, but PG has no 'root' role, so a bare `psql dw_unified` returns
+# nothing and the gate falsely reports 'design not found' (parking every publish
+# on prod). sudo to postgres on Linux; bare psql (trust auth) on macOS.
+_PSQL = (['sudo', '-n', '-u', 'postgres', 'psql', 'dw_unified', '-At', '-F', '|']
+ if sys.platform == 'linux'
+ else ['psql', 'dw_unified', '-At', '-F', '|'])
def psql(q):
- return subprocess.run(['psql', 'dw_unified', '-At', '-F', '|', '-c', q], capture_output=True, text=True).stdout.strip()
+ return subprocess.run(_PSQL + ['-c', q], capture_output=True, text=True).stdout.strip()
-row = psql(f"SELECT COALESCE(local_path,''), COALESCE(kind,''), COALESCE(category,'') FROM all_designs WHERE id={ID};")
+row = psql(f"SELECT COALESCE(local_path,''), COALESCE(kind,''), COALESCE(category,''), COALESCE(image_url,'') FROM all_designs WHERE id={ID};")
if not row: print(json.dumps({'pass': False, 'reasons': ['design not found']})); sys.exit(1)
-lp, kind, category = (row.split('|', 2) + ['', '', ''])[:3]
+lp, kind, category, image_url = (row.split('|', 3) + ['', '', '', ''])[:4]
checks, reasons = {}, []
is_mural = kind in ('mural', 'mural_panel')
from PIL import Image
import numpy as np
-img = None
-if lp and os.path.isfile(lp):
- try: img = Image.open(lp).convert('RGB')
- except Exception: img = None
+# Resolve the image with the same fallback chain as the by-id handler: local_path
+# (often a Mac2-style /Users/... path that doesn't exist on prod), then
+# data/generated/<basename> on THIS box, then the quarantine dirs, then the
+# image_url basename. Without this the gate falsely reports 'image file
+# missing/unreadable' on prod even though the PNG is on disk + served by by-id.
+_IMG_DIR = ROOT / 'data' / 'generated'
+_QUAR = [ROOT / 'data' / 'generated_cactus_quarantine', ROOT / 'data' / 'generated_ghost_quarantine']
+def _resolve_image(lp, image_url):
+ cands = []
+ if lp: cands.append(lp)
+ base = os.path.basename(lp) if lp else ''
+ if base:
+ cands.append(str(_IMG_DIR / base))
+ for q in _QUAR: cands.append(str(q / base))
+ if image_url:
+ iub = os.path.basename(image_url)
+ if iub:
+ cands.append(str(_IMG_DIR / iub))
+ for q in _QUAR: cands.append(str(q / iub))
+ for c in cands:
+ if c and os.path.isfile(c):
+ try: return Image.open(c).convert('RGB')
+ except Exception: pass
+ return None
+img = _resolve_image(lp, image_url)
if img is None:
print(json.dumps({'pass': False, 'id': ID, 'reasons': ['image file missing/unreadable']})); sys.exit(1)
W, H = img.size
← 79f45cc Add 6 room-left PDP theme variants V12-V17 (room setting lef
·
back to Wallco Ai
·
audit: unpublish 20 more flagged-goods from haiku full-sweep dd42e27 →