← back to Wallco Ai
promote_edge_heal: dry-run-default tool, re-scans + flips only fresh-PASS non-ghost edge-heals
dcdbbed4bfa6fc6c5dd611a8d835f1fade0743f1 · 2026-05-26 19:58:18 -0700 · Steve Abrams
Files touched
A scripts/promote_edge_heal.py
Diff
commit dcdbbed4bfa6fc6c5dd611a8d835f1fade0743f1
Author: Steve Abrams <steve@designerwallcoverings.com>
Date: Tue May 26 19:58:18 2026 -0700
promote_edge_heal: dry-run-default tool, re-scans + flips only fresh-PASS non-ghost edge-heals
---
scripts/promote_edge_heal.py | 105 +++++++++++++++++++++++++++++++++++++++++++
1 file changed, 105 insertions(+)
diff --git a/scripts/promote_edge_heal.py b/scripts/promote_edge_heal.py
new file mode 100644
index 0000000..908fa4f
--- /dev/null
+++ b/scripts/promote_edge_heal.py
@@ -0,0 +1,105 @@
+#!/usr/bin/env python3
+"""promote_edge_heal — promote edge-heal copies that pass a FRESH edges-agent scan.
+
+Safe by design:
+ - DRY-RUN by default. Prints what it WOULD promote. Flips nothing.
+ - --apply is required to actually set is_published=TRUE.
+ - Re-scans every candidate live (does NOT trust a stale verdict) — only a
+ fresh edges-agent PASS gets promoted, per "only clean designs welcome".
+ - Excludes any row whose self OR parent carries a BLEED_GHOST flag (a clean
+ edge feather can't fix a ghost source).
+ - Never deploys. After --apply it PRINTS the two commands Steve runs next
+ (refresh snapshot + deploy-kamatera.sh) — it does not run them.
+
+Reversible: each promoted row can be flipped back via
+ POST /api/fix-decision/<id> verdict=reject (or a manual is_published=FALSE).
+
+Usage:
+ python3 scripts/promote_edge_heal.py # dry-run preview
+ python3 scripts/promote_edge_heal.py --apply # actually promote PASSes
+ python3 scripts/promote_edge_heal.py --limit 50 # cap (for a staged rollout)
+"""
+import argparse, subprocess, sys
+from pathlib import Path
+
+sys.path.insert(0, str(Path.home() / '.claude/skills/edges-agent/scripts'))
+import scan as edges
+
+
+def psql(sql):
+ r = subprocess.run(['psql', 'dw_unified', '-At', '-F', '\x1f', '-c', sql],
+ capture_output=True, text=True, check=True)
+ return [ln.split('\x1f') for ln in r.stdout.splitlines() if ln.strip()]
+
+
+def candidates(limit):
+ sql = (
+ "SELECT a.id, a.local_path, a.parent_design_id "
+ "FROM all_designs a "
+ "WHERE a.generator='pil-edge-heal' "
+ " AND (a.is_published IS NULL OR a.is_published=FALSE) "
+ " AND a.local_path IS NOT NULL "
+ " AND a.notes NOT LIKE '%BLEED_GHOST%' "
+ " AND (a.settlement_verdict IS NULL OR a.settlement_verdict NOT LIKE '%GHOST%') "
+ "ORDER BY a.id"
+ )
+ if limit:
+ sql += f" LIMIT {int(limit)}"
+ rows = psql(sql + ";")
+ # parent ghost exclusion
+ pids = sorted({int(r[2]) for r in rows if r[2] not in ('', None)})
+ parent_ghost = set()
+ if pids:
+ pg = psql("SELECT id FROM all_designs WHERE id IN (" +
+ ",".join(str(p) for p in pids) +
+ ") AND (notes LIKE '%BLEED_GHOST%' OR settlement_verdict LIKE '%GHOST%');")
+ parent_ghost = {int(x[0]) for x in pg}
+ return [(int(r[0]), r[1]) for r in rows
+ if r[2] in ('', None) or int(r[2]) not in parent_ghost]
+
+
+def main():
+ ap = argparse.ArgumentParser()
+ ap.add_argument('--apply', action='store_true', help='actually flip is_published=TRUE')
+ ap.add_argument('--limit', type=int, default=None)
+ args = ap.parse_args()
+
+ cands = candidates(args.limit)
+ print(f"candidates (pil-edge-heal, unpublished, non-ghost): {len(cands)}", flush=True)
+
+ passed = []
+ for i, (rid, path) in enumerate(cands, 1):
+ try:
+ res = edges.scan_path(Path(path))
+ if res.get('ok') and res['verdict'] == 'PASS':
+ passed.append(rid)
+ except Exception:
+ pass
+ if i % 200 == 0 or i == len(cands):
+ print(f" re-scanned {i}/{len(cands)} PASS so far={len(passed)}", flush=True)
+
+ print(f"\nfresh-scan PASS (promotable): {len(passed)}")
+ if not passed:
+ print("nothing to promote"); return
+
+ if not args.apply:
+ print("\nDRY-RUN — nothing changed. Re-run with --apply to promote.")
+ print(f"first 15 ids: {passed[:15]}")
+ return
+
+ # --apply: flip in one transaction
+ idlist = ",".join(str(x) for x in passed)
+ r = subprocess.run(
+ ['psql', 'dw_unified', '-q', '-v', 'ON_ERROR_STOP=1', '-c',
+ f"UPDATE all_designs SET is_published=TRUE WHERE id IN ({idlist});"],
+ capture_output=True, text=True)
+ if r.returncode != 0:
+ print(f"PSQL FAILED: {r.stderr[:400]}", file=sys.stderr); sys.exit(1)
+ print(f"\nPROMOTED {len(passed)} rows to is_published=TRUE.")
+ print("\nNEXT (run these yourself — deploy is Steve-gated):")
+ print(" python3 scripts/refresh_designs_snapshot.py")
+ print(" ./deploy-kamatera.sh")
+
+
+if __name__ == '__main__':
+ main()
← fdb9464 Add style-aware-sample-verify.js — settlement + edges-scan +
·
back to Wallco Ai
·
snapshot before reject sweep (preserve parallel-session in-p 5778b04 →