[object Object]

← back to Wallco Ai

Wire seam-fix wave cadence (DTD verdict C): gated one-wave-per-invocation runner + runbook + manifest template; refuses to run until 5-root sample is curate-approved

fc728759c97f326de5c826865ccf00176d545c73 · 2026-06-11 16:07:19 -0700 · Steve Abrams

Files touched

Diff

commit fc728759c97f326de5c826865ccf00176d545c73
Author: Steve Abrams <steve@designerwallcoverings.com>
Date:   Thu Jun 11 16:07:19 2026 -0700

    Wire seam-fix wave cadence (DTD verdict C): gated one-wave-per-invocation runner + runbook + manifest template; refuses to run until 5-root sample is curate-approved
---
 .gitignore               |   3 ++
 SEAM-WAVE-RUNBOOK.md     |  73 ++++++++++++++++++++++++++
 data/seam-wave-roots.txt |  13 +++++
 scripts/seam-wave.sh     | 134 +++++++++++++++++++++++++++++++++++++++++++++++
 4 files changed, 223 insertions(+)

diff --git a/.gitignore b/.gitignore
index 9b156e1..edec7d0 100644
--- a/.gitignore
+++ b/.gitignore
@@ -70,3 +70,6 @@ scripts/vectorize/.venv/
 # refiner comparison renders (regeneratable, served from disk)
 public/refiner-compare/
 .next/
+data/seam-sample-approved.flag
+data/seam-wave-state.json
+data/seam-wave-ledger.jsonl
diff --git a/SEAM-WAVE-RUNBOOK.md b/SEAM-WAVE-RUNBOOK.md
new file mode 100644
index 0000000..0f8e1d7
--- /dev/null
+++ b/SEAM-WAVE-RUNBOOK.md
@@ -0,0 +1,73 @@
+# Seam-fix scale-up — wave cadence runbook
+
+**Decision of record:** DTD verdict **C** (unanimous 3/3, 2026-06-11). Scale the
+native-seamless seam-fix from the 5-root sample to the remaining flagged roots in
+**waves of ~20 with a curate checkpoint between every wave** — never blanket-apply.
+The 261-row luxe revert (2026-05-25) is why this rule exists.
+
+## Why waves (not "batch all")
+The hard edge gate (`verify-edge-seamless.py`, PASS-both-axes) guarantees the
+*tiling* property in code, but it is blind to *aesthetic* regression and to
+fibre-composite failures on root archetypes absent from the 5-root sample. Five
+roots can't represent 85+ heterogeneous roots (busy vs. sparse, light vs. dark
+ground, fine-line vs. mass silhouette). Waves turn an 80-root mistake into a
+20-root one at near-zero added cost — Steve curates every row regardless.
+
+## The pipeline (what a wave actually does)
+Each root → **ControlNet edge-anchor** (preserves the motif so it doesn't drift)
+→ **circular-pad native-seamless tiling** (zero post-hoc smear) → **composite onto
+a real seamless woven-fibre ground** (grasscloth/raffia/linen/sisal/paperweave) →
+**verify-edge-seamless.py** (must PASS both axes). Every candidate lands as a NEW
+id, `is_published=FALSE`, `parent_design_id=<root>`. **Roots are never overwritten.**
+
+The smear techniques are retired for visible output: do NOT use `make_seamless.py`
+or `force-edge-seamless.py`.
+
+## The gate (enforced by `scripts/seam-wave.sh`)
+1. **Curate + approve the 5-root sample first.** Open the seam-fix curator
+   (`http://127.0.0.1:9905/seam-fix-curator.html`), eyeball the ControlNet sample
+   (roots 2662, 54266, 54076, 54610, 53896). When the direction looks right:
+   ```bash
+   touch data/seam-sample-approved.flag    # GATE 1 — without this, no wave runs
+   ```
+2. **Build the manifest** of remaining roots (see below). GATE 2 refuses if empty.
+3. **Run one wave at a time.** The runner never auto-loops — the pause between
+   invocations IS the checkpoint.
+
+## Build the manifest
+The 85 roots already in the curator each have a PASS candidate — they're **done**.
+`data/seam-wave-roots.txt` is for the remaining FAIL roots not yet processed.
+Generate the list from a seam scan of published roots, then write the FAIL ids
+(one per line), excluding any root that already has a PASS candidate in
+`data/seam-fix-queue.jsonl`:
+```bash
+# example — confirm the right scanner + exact output format next session
+python3 scripts/edges-batch-scan.py        # or fuzzy_seam_scan.py / seam_break_scan.py
+# → collect FAIL root ids → data/seam-wave-roots.txt (one per line)
+```
+
+## Confirm the generator name
+`seam-wave.sh` calls `SEAM_GENERATOR` (default `scripts/controlnet-anchored-redo.py`).
+The ControlNet-anchored generator was being finalized in the same session this
+runbook was written — **confirm the final script name + that it accepts
+`--ids a,b,c`** before the first real wave:
+```bash
+SEAM_GENERATOR=scripts/<confirmed-generator>.py bash scripts/seam-wave.sh --dry-run
+```
+
+## Operate
+```bash
+bash scripts/seam-wave.sh --status     # progress + gate state, no generation
+bash scripts/seam-wave.sh --dry-run    # which roots the next wave would do
+bash scripts/seam-wave.sh              # run the next wave (gates must pass)
+# → curate the wave → if quality held, run it again for the next wave
+# → if quality dropped, STOP and fix the pipeline
+```
+Tune wave size with `WAVE_SIZE=20`. State lives in `data/seam-wave-state.json`
+(reset with `rm`), audit trail in `data/seam-wave-ledger.jsonl`.
+
+## Hard constraints (always)
+- Roots sacred — never overwrite; new id, `is_published=FALSE`, `parent_design_id=root`.
+- Never publish to live; the settlement gate still governs publish.
+- A candidate that doesn't PASS both axes is never selectable/publishable.
+- Generation stays PAUSED until GATE 1 (sample approved) — this is deliberate.
diff --git a/data/seam-wave-roots.txt b/data/seam-wave-roots.txt
new file mode 100644
index 0000000..52ea12a
--- /dev/null
+++ b/data/seam-wave-roots.txt
@@ -0,0 +1,13 @@
+# seam-wave-roots.txt — the remaining flagged roots to seam-fix, one id per line.
+# Consumed by scripts/seam-wave.sh in waves of WAVE_SIZE (default 20).
+# Lines starting with '#' and blank lines are ignored.
+#
+# HOW TO POPULATE (next session, after the 5-root sample is curated + approved):
+#   The 85 roots already in the seam-fix curator each have a PASS candidate — they
+#   are DONE. This manifest is for the remaining FAIL roots NOT yet processed.
+#   Generate the list from a seam scan of published roots, e.g.:
+#       python3 scripts/edges-batch-scan.py  # or fuzzy_seam_scan.py / seam_break_scan.py
+#   then write the FAIL root ids here (one per line), excluding any root that already
+#   has a PASS-both-axes candidate in data/seam-fix-queue.jsonl.
+#
+# Until this file has real ids below, scripts/seam-wave.sh GATE 2 will refuse to run.
diff --git a/scripts/seam-wave.sh b/scripts/seam-wave.sh
new file mode 100755
index 0000000..96972c7
--- /dev/null
+++ b/scripts/seam-wave.sh
@@ -0,0 +1,134 @@
+#!/usr/bin/env bash
+# seam-wave.sh — gated wave runner for the seam-fix scale-up.
+#
+# DTD verdict C (2026-06-11, unanimous): scale the native-seamless seam-fix from
+# the 5-root sample to the remaining flagged roots in WAVES (~20 at a time) with a
+# CURATE CHECKPOINT between every wave — never blanket-apply (the 261-row revert
+# of 2026-05-25 is why). This runner enforces that cadence structurally:
+#
+#   • GATE 1 — refuses to run until the 5-root sample is curated + approved
+#     (you create data/seam-sample-approved.flag after eyeballing the sample).
+#   • GATE 2 — refuses if the roots manifest is missing/empty.
+#   • ONE WAVE PER INVOCATION — it never auto-loops. You curate the wave in the
+#     seam-fix curator, then run it again for the next wave. That pause IS the
+#     checkpoint the DTD verdict requires.
+#   • Generator-agnostic — SEAM_GENERATOR points at the final generator (the
+#     ControlNet-anchored native-seamless script). Confirm the name next session.
+#   • Every candidate is verified by verify-edge-seamless.py and must PASS both
+#     axes; roots are never overwritten (the generator lands new ids,
+#     is_published=FALSE, parent_design_id=root).
+#
+# Usage:
+#   bash scripts/seam-wave.sh            # run the next wave (after the gates pass)
+#   bash scripts/seam-wave.sh --status   # show progress, no generation
+#   bash scripts/seam-wave.sh --dry-run  # show which roots the next wave WOULD do
+#
+# Env overrides:
+#   WAVE_SIZE=20                         # roots per wave
+#   SEAM_GENERATOR=scripts/controlnet-anchored-redo.py
+#
+set -euo pipefail
+cd "$(dirname "$0")/.."
+
+WAVE_SIZE="${WAVE_SIZE:-20}"
+GEN="${SEAM_GENERATOR:-scripts/controlnet-anchored-redo.py}"
+MANIFEST="data/seam-wave-roots.txt"          # one root id per line; '#'-comments ok
+STATE="data/seam-wave-state.json"            # {"nextIndex":N} — how far we've gotten
+LEDGER="data/seam-wave-ledger.jsonl"         # append-only audit of every wave
+FLAG="data/seam-sample-approved.flag"        # you create this after curating the sample
+VERIFY="scripts/verify-edge-seamless.py"
+
+MODE="${1:-run}"
+
+# ── roots manifest → array (strip comments + blanks) ────────────────────────────
+read_roots() {
+  [ -f "$MANIFEST" ] || return 0
+  grep -vE '^\s*(#|$)' "$MANIFEST" | tr -d ' \t' || true
+}
+ALL_ROOTS=()
+while IFS= read -r r; do [ -n "$r" ] && ALL_ROOTS+=("$r"); done < <(read_roots)
+TOTAL="${#ALL_ROOTS[@]}"
+
+NEXT=0
+[ -f "$STATE" ] && NEXT="$(node -e 'try{process.stdout.write(String((require("./'"$STATE"'").nextIndex)||0))}catch(e){process.stdout.write("0")}' 2>/dev/null || echo 0)"
+REMAINING=$(( TOTAL - NEXT ))
+
+# ── --status ────────────────────────────────────────────────────────────────────
+if [ "$MODE" = "--status" ]; then
+  echo "seam-wave status"
+  echo "  generator    : $GEN"
+  echo "  wave size    : $WAVE_SIZE"
+  echo "  manifest     : $MANIFEST ($TOTAL roots)"
+  echo "  done so far  : $NEXT"
+  echo "  remaining    : $REMAINING"
+  echo "  waves left   : $(( (REMAINING + WAVE_SIZE - 1) / WAVE_SIZE ))"
+  echo "  sample gate  : $([ -f "$FLAG" ] && echo "APPROVED ✓" || echo "NOT approved ⛔")"
+  exit 0
+fi
+
+# ── GATE 1 — sample approval ─────────────────────────────────────────────────────
+if [ ! -f "$FLAG" ]; then
+  echo "⛔ GATE 1: the 5-root sample has not been curated + approved."
+  echo "   Per DTD verdict C, curate the sample in the seam-fix curator first."
+  echo "   When the direction looks right, approve it with:"
+  echo "       touch $FLAG"
+  exit 1
+fi
+
+# ── GATE 2 — manifest present ────────────────────────────────────────────────────
+if [ "$TOTAL" -eq 0 ]; then
+  echo "⛔ GATE 2: roots manifest $MANIFEST is empty."
+  echo "   Populate it with the remaining flagged roots (one id per line). See"
+  echo "   SEAM-WAVE-RUNBOOK.md → 'Build the manifest' for the seam-scan command."
+  exit 1
+fi
+
+if [ "$REMAINING" -le 0 ]; then
+  echo "✅ All $TOTAL manifest roots have been run. Nothing left. (Reset: rm $STATE)"
+  exit 0
+fi
+
+# ── compute this wave's slice ────────────────────────────────────────────────────
+WAVE_END=$(( NEXT + WAVE_SIZE )); [ "$WAVE_END" -gt "$TOTAL" ] && WAVE_END="$TOTAL"
+WAVE_IDS=()
+for (( i=NEXT; i<WAVE_END; i++ )); do WAVE_IDS+=("${ALL_ROOTS[$i]}"); done
+IDS_CSV="$(IFS=,; echo "${WAVE_IDS[*]}")"
+WAVE_NO=$(( NEXT / WAVE_SIZE + 1 ))
+
+if [ "$MODE" = "--dry-run" ]; then
+  echo "DRY RUN — wave $WAVE_NO would generate ${#WAVE_IDS[@]} roots:"
+  echo "  $IDS_CSV"
+  echo "  generator: python3 $GEN --ids $IDS_CSV"
+  exit 0
+fi
+
+# ── run the wave ─────────────────────────────────────────────────────────────────
+echo "▶ Wave $WAVE_NO — generating ${#WAVE_IDS[@]} roots (${NEXT}..$((WAVE_END-1)) of $TOTAL)"
+echo "  ids: $IDS_CSV"
+[ -f "$GEN" ] || { echo "⛔ generator not found: $GEN — set SEAM_GENERATOR to the final script."; exit 1; }
+
+set +e
+python3 "$GEN" --ids "$IDS_CSV"
+GEN_RC=$?
+set -e
+
+# verify (the generator already runs verify-edge-seamless.py per-candidate; this is
+# a belt-and-suspenders wave-level confirmation that the gate exists + ran)
+[ -f "$VERIFY" ] || echo "⚠ verifier $VERIFY missing — candidates are NOT edge-verified; do not publish."
+
+# advance state + ledger only if generation succeeded
+if [ "$GEN_RC" -eq 0 ]; then
+  node -e 'const fs=require("fs");fs.writeFileSync("'"$STATE"'",JSON.stringify({nextIndex:'"$WAVE_END"',updatedAt:new Date().toISOString()}))' 2>/dev/null \
+    || printf '{"nextIndex":%s}\n' "$WAVE_END" > "$STATE"
+  printf '{"wave":%s,"ids":"%s","count":%s,"rc":%s}\n' "$WAVE_NO" "$IDS_CSV" "${#WAVE_IDS[@]}" "$GEN_RC" >> "$LEDGER"
+fi
+
+NEW_REMAINING=$(( TOTAL - WAVE_END ))
+echo
+echo "──────────────────────────────────────────────────────────────────"
+echo "  ✋ CURATE CHECKPOINT (DTD verdict C)"
+echo "  Wave $WAVE_NO done (rc=$GEN_RC). $NEW_REMAINING roots remain."
+echo "  → Curate this wave in the seam-fix curator: http://127.0.0.1:9905/seam-fix-curator.html"
+echo "  → If quality HELD, run the next wave:  bash scripts/seam-wave.sh"
+echo "  → If quality DROPPED, STOP and fix the pipeline before continuing."
+echo "──────────────────────────────────────────────────────────────────"

← 3a53bd5 wallco redo: real-fibre composite path + img2img native-seam  ·  back to Wallco Ai  ·  Add controlnet-anchored-redo.py: SDXL canny-CN anchored nati c63a1ff →