[object Object]

← back to Patterndesignlab

Exclude settlement vision_blocked/vision_review designs from storefront (designs/facets/detail/designer); surface status in admin for audit

df1617fcaa0dca8c4b867f1e5af34498adfb06e3 · 2026-07-04 13:40:23 -0700 · Steve

Files touched

Diff

commit df1617fcaa0dca8c4b867f1e5af34498adfb06e3
Author: Steve <steve@designerwallcoverings.com>
Date:   Sat Jul 4 13:40:23 2026 -0700

    Exclude settlement vision_blocked/vision_review designs from storefront (designs/facets/detail/designer); surface status in admin for audit
---
 server.js | 23 ++++++++++++++++-------
 1 file changed, 16 insertions(+), 7 deletions(-)

diff --git a/server.js b/server.js
index e69760b..dc43625 100644
--- a/server.js
+++ b/server.js
@@ -44,6 +44,13 @@ app.get('/api/config', (req, res) => res.json({
   note: stripe ? 'TEST MODE — no real charges' : 'test keys not yet installed — inquiries only',
 }));
 
+// ---- served-visibility gate ----
+// The settlement post-gen VISION pass flags designs whose local master image fails the
+// settlement gate. 'vision_blocked' (full Part A + Part B violation) and 'vision_review'
+// (fail-closed / ambiguous) are EXCLUDED from everything served to the storefront, but stay
+// in the DB for audit. NULL (unscreened / hand-curated) and 'vision_ok' are served.
+const servedClause = (alias) => `COALESCE(${alias}.settlement_status,'') NOT IN ('vision_blocked','vision_review')`;
+
 // ---- search + faceted browse ----
 const SORTS = {
   newest:   'x.created_at DESC',
@@ -55,7 +62,7 @@ const SORTS = {
   price_dn: 'x.price_min DESC NULLS LAST, x.title ASC',
 };
 function buildWhere(q, params) {
-  const w = [];
+  const w = [servedClause('x')]; // storefront never serves settlement-blocked/review designs
   if (q.q) { params.push(q.q); w.push(`x.search_tsv @@ plainto_tsquery('english', $${params.length})`); }
   for (const [key, col] of [['style','style'],['colorway','colorway'],['motif','motif'],['technique','technique']]) {
     if (q[key]) { params.push(q[key]); w.push(`x.${col} = $${params.length}`); }
@@ -118,11 +125,11 @@ app.get('/api/designs', async (req, res) => {
 app.get('/api/facets', async (req, res) => {
   try {
     const facet = async (col) => (await pool.query(
-      `SELECT ${col} AS value, count(*)::int AS n FROM designs WHERE ${col} IS NOT NULL GROUP BY ${col} ORDER BY n DESC, value ASC`)).rows;
-    const price = (await pool.query('SELECT min(price_min)::float lo, max(price_min)::float hi FROM designs')).rows[0];
+      `SELECT ${col} AS value, count(*)::int AS n FROM designs WHERE ${col} IS NOT NULL AND ${servedClause('designs')} GROUP BY ${col} ORDER BY n DESC, value ASC`)).rows;
+    const price = (await pool.query(`SELECT min(price_min)::float lo, max(price_min)::float hi FROM designs WHERE ${servedClause('designs')}`)).rows[0];
     // Collection facet counts — buyer-friendly labels applied in the UI, internal class values here.
     const cc = (await pool.query(
-      `SELECT content_class AS value, count(*)::int AS n FROM designs WHERE content_class IS NOT NULL GROUP BY content_class`)).rows;
+      `SELECT content_class AS value, count(*)::int AS n FROM designs WHERE content_class IS NOT NULL AND ${servedClause('designs')} GROUP BY content_class`)).rows;
     const collection = {
       credible: (cc.find(r => r.value === 'credible') || {}).n || 0,
       novelty:  (cc.find(r => r.value === 'novelty')  || {}).n || 0,
@@ -139,7 +146,8 @@ app.get('/api/designs/:id', async (req, res) => {
   try {
     const r = await pool.query(
       `SELECT x.*, d.name AS designer_name, d.slug AS designer_slug, d.country AS designer_country
-       FROM designs x JOIN designers d ON d.id=x.designer_id WHERE x.id=$1 OR x.slug=$1`, [req.params.id]);
+       FROM designs x JOIN designers d ON d.id=x.designer_id
+       WHERE (x.id=$1 OR x.slug=$1) AND ${servedClause('x')}`, [req.params.id]);
     if (!r.rowCount) return res.status(404).json({ error: 'not found' });
     res.json(r.rows[0]);
   } catch (e) { console.error(e); res.status(500).json({ error: 'query failed' }); }
@@ -151,7 +159,7 @@ app.get('/api/designers/:slug', async (req, res) => {
     if (!d.rowCount) return res.status(404).json({ error: 'not found' });
     const items = await pool.query(
       `SELECT x.id,x.slug,x.title,x.style,x.colorway,x.dominant_hex,x.img,x.price_min,x.seamless,x.created_at
-       FROM designs x WHERE x.designer_id=$1 ORDER BY x.created_at DESC`, [d.rows[0].id]);
+       FROM designs x WHERE x.designer_id=$1 AND ${servedClause('x')} ORDER BY x.created_at DESC`, [d.rows[0].id]);
     res.json({ designer: d.rows[0], designs: items.rows, count: items.rowCount });
   } catch (e) { console.error(e); res.status(500).json({ error: 'query failed' }); }
 });
@@ -184,7 +192,8 @@ app.get('/api/admin/designs', async (req, res) => {
   try {
     const r = await pool.query(
       `SELECT x.id,x.title,x.style,x.colorway,x.motif,x.technique,x.seamless,x.dominant_hex,
-         x.img,x.price_min,x.price_max,x.pricing_status,x.created_at,d.name AS designer_name
+         x.img,x.price_min,x.price_max,x.pricing_status,x.created_at,d.name AS designer_name,
+         x.settlement_status,x.settlement_checked_at
        FROM designs x JOIN designers d ON d.id=x.designer_id ORDER BY x.created_at DESC`);
     res.json({ count: r.rowCount, designs: r.rows });
   } catch (e) { console.error(e); res.status(500).json({ error: 'query failed' }); }

← 7136fea Add settlement post-gen vision pass (Gemini 2.5-flash, one c  ·  back to Patterndesignlab  ·  Add .deploy.conf for prestige.agentabrams.com go-live 3708590 →