[object Object]

← back to York Reprice 2026 08

York cadence auto-onboard: DRAFT drip cap 100/run (Steve-approved), --limit now caps NEW creates for daily advance; smoke-3 verified live

e2a5144ecab0030a7ef5a493cc839a33f7e510cb · 2026-07-13 10:31:53 -0700 · Steve

Files touched

Diff

commit e2a5144ecab0030a7ef5a493cc839a33f7e510cb
Author: Steve <steve@designerwallcoverings.com>
Date:   Mon Jul 13 10:31:53 2026 -0700

    York cadence auto-onboard: DRAFT drip cap 100/run (Steve-approved), --limit now caps NEW creates for daily advance; smoke-3 verified live
---
 cadence/york-auto-onboard.mjs | 57 +++++++++++++++++++++++++++++++++++++++++++
 cadence/york-cadence.mjs      | 11 +++++++++
 york-new-create.mjs           |  7 ++++--
 3 files changed, 73 insertions(+), 2 deletions(-)

diff --git a/cadence/york-auto-onboard.mjs b/cadence/york-auto-onboard.mjs
new file mode 100644
index 0000000..6f635db
--- /dev/null
+++ b/cadence/york-auto-onboard.mjs
@@ -0,0 +1,57 @@
+#!/usr/bin/env node
+/**
+ * york-auto-onboard.mjs — the ONE place the York cadence is allowed to WRITE to Shopify.
+ *
+ * Steve authorized (2026-07-13, AskUserQuestion): the cadence auto-onboards New York items as
+ * DRAFT, capped ~100 per run. This drips the pre-vetted onboard queue
+ * (data/onboard-payloads-enriched.json) into Shopify over successive mornings until exhausted.
+ *
+ * HARD RAILS (never widened here):
+ *   • DRAFT only — york-new-create sets status:'DRAFT'; nothing goes ACTIVE. Publish stays a
+ *     separate, explicit, Steve-gated step.
+ *   • Cap 100 NEW creates per run (--limit); idempotent — SKUs already live are skipped.
+ *   • ONBOARD bucket only. Reprice + disco remain strictly propose-only (memo, never auto-write).
+ *   • The queue is PRE-VETTED: settlement-deepened (flora/fauna held), 39 image-flags excluded,
+ *     dead-CDN images dropped, priced (naturals@roll_price_single, standard@MAP), gate-clean.
+ *   • Fail-safe: any error is caught and logged; it never throws up into the cadence diff.
+ *
+ * Called at the tail of york-cadence.mjs. Returns {created,skipped,err,total_remaining}.
+ */
+import fs from 'node:fs';
+import { execSync } from 'node:child_process';
+
+const ROOT = process.env.HOME + '/Projects/york-reprice-2026-08';
+const QUEUE = ROOT + '/data/onboard-payloads-enriched.json';
+const CAP = 100;
+
+export function autoOnboard() {
+  const result = { created: 0, skipped: 0, err: 0, queue_size: 0, ran: false, note: '' };
+  try {
+    if (!fs.existsSync(QUEUE)) { result.note = 'no onboard queue file — nothing to onboard'; return result; }
+    const queue = JSON.parse(fs.readFileSync(QUEUE, 'utf8'));
+    result.queue_size = queue.length;
+    if (!queue.length) { result.note = 'onboard queue empty'; return result; }
+
+    // Spawn the vetted creator (DRAFT, idempotent, cap=CAP new creates). cwd=ROOT for its relative paths.
+    const out = execSync(
+      `node york-new-create.mjs --file=data/onboard-payloads-enriched.json --limit=${CAP} --apply --i-am-steve`,
+      { cwd: ROOT, encoding: 'utf8', timeout: 30 * 60 * 1000 }
+    );
+    result.ran = true;
+    // parse the creator's DONE line: "DONE — N DRAFT created, M create-err, K skipped(exists), ..."
+    const m = out.match(/DONE — (\d+) DRAFT created, (\d+) create-err, (\d+) skipped/);
+    if (m) { result.created = +m[1]; result.err = +m[2]; result.skipped = +m[3]; }
+    result.note = m ? `created ${result.created}, skipped(exists) ${result.skipped}, err ${result.err}` : 'ran; could not parse creator output';
+    // tail of creator output for the log
+    result.tail = out.trim().split('\n').slice(-4).join(' | ');
+  } catch (e) {
+    result.note = 'auto-onboard error (caught, cadence unaffected): ' + String(e.message || e).slice(0, 160);
+  }
+  return result;
+}
+
+// Standalone run (for testing / manual drip): `node cadence/york-auto-onboard.mjs`
+if (import.meta.url === `file://${process.argv[1]}`) {
+  const r = autoOnboard();
+  console.log('york-auto-onboard:', JSON.stringify(r, null, 2));
+}
diff --git a/cadence/york-cadence.mjs b/cadence/york-cadence.mjs
index 12c559f..3f00321 100644
--- a/cadence/york-cadence.mjs
+++ b/cadence/york-cadence.mjs
@@ -10,6 +10,7 @@
  * NEVER writes to Shopify. Gated actions stay Steve's (existing york-reprice/create/disco tooling).
  */
 import fs from 'node:fs'; import { execSync } from 'node:child_process';
+import { autoOnboard } from './york-auto-onboard.mjs';  // the ONE Shopify write: New→DRAFT, cap 100/run
 const ROOT=process.env.HOME+'/Projects/york-reprice-2026-08';
 const SHOP='designer-laboratory-sandbox.myshopify.com',VER='2024-10';
 const TOKEN=(fs.readFileSync(process.env.HOME+'/Projects/secrets-manager/.env','utf8').match(/^SHOPIFY_ADMIN_TOKEN=(.+)$/m)||[])[1]?.trim();
@@ -92,3 +93,13 @@ if(actionable>0){
   fs.writeFileSync(process.env.HOME+'/.claude/yolo-queue/pending-approval/york-cadence-actions.md',memo);
   try{execSync(`curl -s -X POST http://127.0.0.1:3333/api/parking-lot -H 'Content-Type: application/json' -d ${JSON.stringify(JSON.stringify({project:'designerwallcoverings',title:`York cadence: ${actionable} actionable`,note:`reprice ${reprice.length} / onboard ${onboardReady} / disco ${disco.length}`}))}`,{stdio:'ignore'});}catch(e){}
 }
+
+// ---- AUTO-ONBOARD — the ONLY Shopify write this cadence performs (Steve-authorized 2026-07-13) ----
+// Drips the pre-vetted onboard queue (New York items) into Shopify as DRAFT, cap 100/run, idempotent.
+// Reprice + disco above stay strictly PROPOSE-ONLY (memo only). Publish DRAFT→ACTIVE stays gated.
+const ob=autoOnboard();
+console.log(`york-auto-onboard | ${ob.ran?'RAN':'skipped'} | ${ob.note}`);
+if(ob.created>0){
+  try{execSync(`curl -s -X POST http://127.0.0.1:3333/api/wins -H 'Content-Type: application/json' -d ${JSON.stringify(JSON.stringify({project:'york-reprice-2026-08',title:`York cadence auto-onboarded ${ob.created} DRAFT`,summary:`${ob.note}; queue ${ob.queue_size}. DRAFT-only, cap 100/run, idempotent.`}))}`,{stdio:'ignore'});}catch(e){}
+  try{fs.appendFileSync(process.env.HOME+'/.claude/yolo-queue/pending-approval/york-cadence-actions.md',`\n\n### Auto-onboard ${out.ts.slice(0,10)}\n- Created **${ob.created}** New York items as DRAFT (cap 100/run). ${ob.note}. Queue size ${ob.queue_size}.\n- DRAFT only — publish to ACTIVE remains a separate gated step.\n`);}catch(e){}
+}
diff --git a/york-new-create.mjs b/york-new-create.mjs
index 49112c2..9c6967e 100644
--- a/york-new-create.mjs
+++ b/york-new-create.mjs
@@ -17,10 +17,12 @@ const gql=async(q,v)=>{for(let a=0;a<8;a++){let j;try{const r=await fetch(URL,{m
 const typeOf=k=> k==='custom.color_details'?'json' : (/_percentage$/.test(k)||/(us_msrp|wholesale_price|dw_price_bolt)$/.test(k))?'number_decimal' : 'single_line_text_field';
 const FILE=args.file||'data/york-new-phase1-payloads-enriched.json';
 let items=JSON.parse(fs.readFileSync(FILE,'utf8'));
-if(Number.isFinite(LIMIT)) items=items.slice(0,LIMIT);
+// NOTE: --limit caps the number of NEW products CREATED (not a slice of input). The loop scans the
+// whole file, idempotency-skips SKUs already live, and stops after LIMIT successful creates. This is
+// what lets a daily cadence drip the next N un-onboarded items each run without re-slicing the head.
 const bad=items.filter(p=>!p.title||!p.handle||!(p.variants?.length===2)||!(+p.variants[1].price>0)||p.status!=='DRAFT'||!p.metafields||!p.image||!(p.collections?.length));
 const uomDist={};for(const p of items)uomDist[p.uom||'?']=(uomDist[p.uom||'?']||0)+1;
-console.log(`york-new-create — ${APPLY?'⚠️ LIVE CREATE (DRAFT)':'DRY-RUN'} | file: ${FILE} | items: ${items.length}`);
+console.log(`york-new-create — ${APPLY?'⚠️ LIVE CREATE (DRAFT)':'DRY-RUN'} | file: ${FILE} | items: ${items.length} | cap(new creates): ${Number.isFinite(LIMIT)?LIMIT:'∞'}`);
 console.log(`  validation: ${items.length-bad.length} well-formed, ${bad.length} incomplete | UOM: ${JSON.stringify(uomDist)}`);
 console.log(`  SKU range ${items[0].variants[1].sku}..${items.at(-1).variants[1].sku} | idempotent(SKU-guard) + collectionAddProducts: enabled`);
 if(bad.length){bad.slice(0,5).forEach(p=>console.log('   incomplete:',p.mfr_sku,p.title));}
@@ -38,6 +40,7 @@ const Madd=`mutation($id:ID!,$pids:[ID!]!){collectionAddProducts(id:$id,productI
 const Sku=`query($q:String!){productVariants(first:1,query:$q){nodes{id}}}`;
 let done=0,err=0,skip=0,colErr=0,missCol=0;
 for(const p of items){
+  if(done>=LIMIT){console.log(`  cap reached: ${done} created (--limit=${LIMIT})`);break;}
   const rollSku=p.variants[1].sku;
   // idempotency: roll SKU already live?
   const ex=await gql(Sku,{q:`sku:${rollSku}`});

← fe390d8 auto-save: 2026-07-13T10:24:15 (7 files) — data/onboard-payl  ·  back to York Reprice 2026 08  ·  chore: fail-fast token guard in york-new-create (session-clo 2ee4bcf →