[object Object]

← back to Wallco Ai

feat(curator): force-publish stoned-animals (Steve explicit override 2026-05-29)

6e172f4ec9bbbae774ff63d2a7d80a243f1960b4 · 2026-05-29 13:00:12 -0700 · Steve Abrams

716 stoned-animals were bulk-marked 'bad' 2026-05-28 (user_removed) and fail the
100% gate (seam + off-concept). Steve explicitly overrode both removal and gate.
force-publish-stoned.sql flips is_published in all_designs + spoon_all_designs
under SET LOCAL settlement.allow_override, tags curator-force-publish (exempts the
load-time passesAestheticGate), then snapshot rebuild + ship makes them live.
publish-stoned-animals.js is the gated alternative (kept for future categories).

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

Files touched

Diff

commit 6e172f4ec9bbbae774ff63d2a7d80a243f1960b4
Author: Steve Abrams <steve@designerwallcoverings.com>
Date:   Fri May 29 13:00:12 2026 -0700

    feat(curator): force-publish stoned-animals (Steve explicit override 2026-05-29)
    
    716 stoned-animals were bulk-marked 'bad' 2026-05-28 (user_removed) and fail the
    100% gate (seam + off-concept). Steve explicitly overrode both removal and gate.
    force-publish-stoned.sql flips is_published in all_designs + spoon_all_designs
    under SET LOCAL settlement.allow_override, tags curator-force-publish (exempts the
    load-time passesAestheticGate), then snapshot rebuild + ship makes them live.
    publish-stoned-animals.js is the gated alternative (kept for future categories).
    
    Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---
 scripts/force-publish-stoned.sql  | 35 +++++++++++++++++
 scripts/publish-stoned-animals.js | 82 +++++++++++++++++++++++++++++++++++++++
 2 files changed, 117 insertions(+)

diff --git a/scripts/force-publish-stoned.sql b/scripts/force-publish-stoned.sql
new file mode 100644
index 0000000..6530e98
--- /dev/null
+++ b/scripts/force-publish-stoned.sql
@@ -0,0 +1,35 @@
+-- Force-publish the stoned-animals curator queue (Steve explicit override 2026-05-29).
+-- These 716 were bulk-marked 'bad' on 2026-05-28 (user_removed=TRUE) and fail the
+-- 100% publish gate (seam ΔE>5 + off-concept). Steve explicitly chose to override
+-- BOTH the prior removal AND the gate. SET LOCAL settlement.allow_override bypasses
+-- the PG settlement_publish_check() trigger that would otherwise silently no-op the
+-- is_published flip. Writes BOTH all_designs (curator state) and spoon_all_designs
+-- (the table refresh_designs_snapshot.py reads → the live catalog).
+BEGIN;
+SET LOCAL settlement.allow_override = 'true';
+
+UPDATE all_designs SET
+    is_published   = TRUE,
+    web_viewer     = TRUE,
+    user_removed   = FALSE,
+    needs_fixing_at= NULL,
+    digital_file_at= NULL,
+    tags = array_remove(array_remove(COALESCE(tags, ARRAY[]::text[]), 'needs-fixing'), 'digital-file')
+           || ARRAY['curator-force-publish']::text[],
+    notes = COALESCE(notes,'') || ' | curator-force-publish stoned-animals 2026-05-29'
+WHERE category ILIKE '%stoned-animals%'
+  AND local_path IS NOT NULL
+  AND local_path NOT LIKE '%midheal_%' AND local_path NOT LIKE '%edgeheal_%' AND local_path NOT LIKE '%smartfix_%'
+  AND NOT EXISTS (SELECT 1 FROM wallco_etsy_bucket eb WHERE eb.design_id = all_designs.id AND eb.state IN ('queued','exported','listed','sold'))
+  AND is_published = FALSE;
+
+-- Mirror into the snapshot source for exactly the rows we just force-published.
+UPDATE spoon_all_designs s SET
+    is_published = TRUE,
+    user_removed = FALSE
+FROM all_designs a
+WHERE s.id = a.id
+  AND a.category ILIKE '%stoned-animals%'
+  AND 'curator-force-publish' = ANY(a.tags);
+
+COMMIT;
diff --git a/scripts/publish-stoned-animals.js b/scripts/publish-stoned-animals.js
new file mode 100644
index 0000000..a7081f8
--- /dev/null
+++ b/scripts/publish-stoned-animals.js
@@ -0,0 +1,82 @@
+#!/usr/bin/env node
+/**
+ * Gated bulk-publish for the stoned-animals curator queue (Steve 2026-05-29).
+ *
+ * For each unpublished stoned-animals design (curator's exact filter), run the
+ * 100% publish gate (scripts/publish-gate.py — llava vision + seam). On PASS,
+ * flip is_published in BOTH all_designs (curator's table) AND spoon_all_designs
+ * (the table refresh_designs_snapshot.py reads → the live catalog). On FAIL,
+ * park it (needs_fixing_at + curator-loved tag) exactly like applyCactusDecision
+ * 'live' gate-held path. Crash-safe + resumable: only ever queries the still-
+ * unpublished set, writes per-id immediately, logs progress.
+ *
+ *   node scripts/publish-stoned-animals.js [--limit N] [--dry-run]
+ */
+const { execSync } = require('child_process');
+const path = require('path');
+
+const LIMIT = (() => { const i = process.argv.indexOf('--limit'); return i > -1 ? parseInt(process.argv[i + 1], 10) : 0; })();
+const DRY = process.argv.includes('--dry-run');
+const DB = 'dw_unified';
+
+const oneline = (sql) => sql.replace(/\s+/g, ' ').trim();
+function q(sql) { return execSync(`psql ${DB} -tAc ${JSON.stringify(oneline(sql))}`, { encoding: 'utf8' }).trim(); }
+function exec(sql) { execSync(`psql ${DB} -tAc ${JSON.stringify(oneline(sql))}`, { encoding: 'utf8' }); }
+
+// Curator queue (include=removed) for stoned-animals: ILIKE %stoned-animals%,
+// real local_path, no heal-derivative, not in etsy bucket, still unpublished.
+// NOTE: these 716 are all user_removed=TRUE (bulk-'bad' 2026-05-28). Steve's
+// "publish all" = resurrect+publish. We INCLUDE removed here; the gate decides.
+const FILTER = `
+  category ILIKE '%stoned-animals%'
+  AND local_path IS NOT NULL
+  AND local_path NOT LIKE '%midheal_%' AND local_path NOT LIKE '%edgeheal_%' AND local_path NOT LIKE '%smartfix_%'
+  AND NOT EXISTS (SELECT 1 FROM wallco_etsy_bucket eb WHERE eb.design_id=all_designs.id AND eb.state IN ('queued','exported','listed','sold'))
+  AND is_published = FALSE`;
+
+let ids = q(`SELECT id FROM all_designs WHERE ${FILTER} ORDER BY id;`).split('\n').filter(Boolean).map(Number);
+if (LIMIT > 0) ids = ids.slice(0, LIMIT);
+
+console.log(`[publish-stoned] ${ids.length} unpublished to gate${DRY ? ' (DRY RUN)' : ''}  ${new Date().toISOString()}`);
+
+let published = 0, parked = 0, errored = 0, trigblock = 0;
+const GATE_ENV = 'OLLAMA_VISION_HOST=127.0.0.1:11434 OLLAMA_VISION_MODEL=llava:latest';
+const GATE = path.join(__dirname, 'publish-gate.py');
+
+function gate(id) {
+  try {
+    const out = execSync(`${GATE_ENV} python3 ${GATE} --id ${id}`, { encoding: 'utf8', timeout: 95000, maxBuffer: 10_000_000 });
+    return JSON.parse(out.trim().split('\n').pop());
+  } catch (e) {
+    try { return JSON.parse((e.stdout || '').trim().split('\n').pop()); } catch {}
+    return { pass: false, reasons: ['gate error: ' + String(e.message || '').slice(0, 80)] };
+  }
+}
+
+for (let i = 0; i < ids.length; i++) {
+  const id = ids[i];
+  let verdict;
+  try { verdict = gate(id); } catch (e) { verdict = { pass: false, reasons: ['exc'] }; }
+  if (DRY) {
+    console.log(`  [${i + 1}/${ids.length}] #${id}  ${verdict.pass ? 'PASS' : 'park: ' + (verdict.reasons || []).join(';').slice(0, 60)}`);
+    verdict.pass ? published++ : parked++;
+  } else if (verdict.pass) {
+    // Publish in BOTH tables. all_designs = curator state; spoon_all_designs =
+    // snapshot source. A PG settlement trigger can silently no-op the UPDATE, so
+    // we read back is_published and only count a real publish.
+    exec(`UPDATE all_designs SET is_published=TRUE, web_viewer=TRUE, user_removed=FALSE, needs_fixing_at=NULL, digital_file_at=NULL, tags=array_remove(array_remove(COALESCE(tags,ARRAY[]::text[]),'needs-fixing'),'digital-file') WHERE id=${id};`);
+    const ok = q(`SELECT is_published FROM all_designs WHERE id=${id};`) === 't';
+    if (ok) {
+      exec(`UPDATE spoon_all_designs SET is_published=TRUE WHERE id=${id};`);
+      published++;
+    } else { trigblock++; }
+  } else {
+    // Gate failed — LEAVE the design exactly as it is (still removed/unpublished).
+    // We do NOT resurrect or re-park gate-failures; only clean designs go live.
+    parked++;
+  }
+  if ((i + 1) % 20 === 0 || i === ids.length - 1) {
+    console.log(`  [${i + 1}/${ids.length}] published=${published} parked=${parked} trigger-blocked=${trigblock} err=${errored}  ${new Date().toISOString()}`);
+  }
+}
+console.log(`[publish-stoned] DONE published=${published} parked=${parked} trigger-blocked=${trigblock}  ${new Date().toISOString()}`);

← c9f199f Honor force in bulk /api/cactus-decision live path — bulk Pu  ·  back to Wallco Ai  ·  audit: bulk Bad 39 flagged-live designs from haiku full-swee b75d7f3 →