← 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
A cadence/york-auto-onboard.mjsM cadence/york-cadence.mjsM york-new-create.mjs
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 →